opencode

package module
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 17 Imported by: 0

README

opencode-go-sdk-lite

opencode v1 HTTP API 的轻量 Go SDK,纯标准库实现。

覆盖范围:

  • 会话管理(创建 / 查询 / 更新 / 删除)与对话发起(prompt / 中断 / 历史;agent/model 随 prompt body 指定)
  • SSE 订阅(过程消息、权限请求、问题请求、最终回复)+ 自动断线重连
  • 可用模型 / provider / agent / skill / 命令查询
  • 权限与问题应答

安装

go get github.com/justphantom/opencode-go-sdk-lite

接口清单

Client 配置
Go API 说明
New(baseURL, opts...) 构造 Client;baseURL 形如 http://127.0.0.1:4096
WithToken(t) 设置 Authorization: Bearer <t>
WithBasicAuth(u, p) 设置 HTTP Basic 认证(与 WithToken 互斥,后者覆盖前者)
WithPassword(p) serve 密码模式快捷方式(Basic,用户名固定 opencode
WithHTTPClient(c) 注入自定义 *http.Client
WithHeader(k, v) 追加/覆盖单个请求头
WithUserAgent(ua) 设置 User-Agent
WithLogger(l) (v0.2) 注入 *slog.Logger,覆盖 connect/dispatch/watchdog 等埋点(默认 discard)
WithBusinessIdleTimeout(d) (v0.2) 业务事件空闲阈值,超过触发 OnIdle(默认 5min)
WithDrainGrace(d) (v0.2) pump ctx 取消后等飞行中终止事件的宽限(默认 500ms,负值禁用)
Session 管理 — session.go
Go API HTTP 说明
CreateSession(ctx, *CreateSessionReq) POST /session Directory 走平铺 query,其余进 body
ListSessions(ctx, *ListSessionsOpt) GET /session 裸数组无游标;serve 默认 limit=100 会截断,SDK 默认上送 limit=200,更多需显式 Limit
GetSession(ctx, sessionID) GET /session/{id}
DeleteSession(ctx, id) DELETE /session/{id} 返回 false 视为错误
SessionStatuses(ctx) GET /session/status map[sessionID]运行状态;空闲会话可能缺省,缺省即 idle(session.go:218)
DeleteSessionIfIdle(ctx, id) (内部 SessionStatuses + DELETE) busy 时拒绝且不发 DELETE;状态查询失败透传错误;查询与删除间有竞态,尽力而为(session.go:205)
Prompt(ctx, id, *PromptReq) (*PromptAck, error) POST /session/{id}/prompt_async 204 无 body;messageID/partID 由 SDK 生成并经 ack 回传;支持 model/agent/variant/system/tools 开关与 text/file 附件 part
Interrupt(ctx, id) POST /session/{id}/abort 空闲时为 no-op
UpdateSession(ctx, id, *UpdateSessionReq) PATCH /session/{id} 改标题/元数据/归档时间,返回更新后会话
ListMessages(ctx, id, *ListMessagesOpt) GET /session/{id}/message 元素为 {info, parts}SessionMessage.FinalText() 重组最终回复(过滤 synthetic/ignored),SSE 断连后兜底用;SessionMessage.ReasoningText() 重组思考内容(reasoning part,多段 "\n" 连接)
GetMessage(ctx, sessionID, messageID) GET /session/{id}/message/{messageID} 单条消息(info+parts);终止后取服务端落库最终回复用(session.go:264)
Client / Agent / Health — client.go / agent.go
Go API HTTP 说明
Health(ctx) GET /global/health 响应 {healthy:true}
ListAgents(ctx, *LocationRef) GET /agent 定位参数为平铺 query(directory/workspace)
ListSkills(ctx, *LocationRef) GET /skill 可用 skill(name/description/location/content)
ListCommands(ctx, *LocationRef) GET /command 可用命令(source ∈ command/mcp/skill)
SSE 订阅与断线重连 — event.go / sse.go / globalstream.go
Go API HTTP 说明
SessionEvents(ctx, id, *SessionEventsOpt) 返回 (<-chan Event, <-chan error) GET /event 全局流按 sessionID 过滤;无续传
NewGlobalEventStream(ctx, *LocationRef) 返回 *GlobalEventStream GET /event 按 sessionID 路由的全局连接
stream.Subscribe(sessionID) / Unsubscribe(id) / Close() (基于上一行) 多路复用
Run(ctx, stream, RunOptions) 返回 (<-chan HighEvent, error) 串联 prompt + 订阅 + 过滤 + 合成终止 详见「高层 Run + HighEvent」段

SessionEvents:连接全局 /event 后按 sessionID 过滤;指数退避(默认 500ms→30s); 4xx(除 429)视为不可恢复写 errc 后停止。无续传,断连窗口的事件会丢失。

事件总线按 directory 隔离(实测)SessionEventsOpt.LocationNewGlobalEventStreamloc 必须与目标会话的 directory 一致, 否则收不到这些会话的事件(只能收到默认目录的)。

全局流GlobalEventStream):指数退避 100ms→5s(连接存活 <2s 视为 flapping 不重置退避); 心跳 watchdog 15s 无帧强制重连;panic recover。

事件类型见 types.goEventXxx 常量(V1 经典事件体系:message.part.* / message.updated / session.* / permission.asked / question.asked 等;实测不产生 session.next.* 事件)。 Event.Properties 为原始 JSON,调用方按 Type 自行反序列化;高频事件附 struct: PartDeltaData / PartUpdatedData / MessageUpdatedData / PermissionAskedData / QuestionAskedData / SessionIdleData / SessionErrorData / TodoUpdatedData

工具调用事件可用 ClassifyTool(name) 归类:file_read / file_write / shell / search / webfetch / mcp / subagent / todo / other(MCP 工具无统一前缀,归类为尽力而为); Run 产出的 tool_use / tool_result 事件经 ToolKind() 直接读取。

可用模型与 Provider — model.go

V1 无独立模型目录,三者统一走 GET /provider(响应 {all, default, connected},模型内嵌于 provider):

Go API 说明
ListModels(ctx, *LocationRef) 拍平 all[].modelsEnabledstatus=="active" 推导
ListProviders(ctx, *LocationRef) 返回 all
GetProvider(ctx, providerID, *LocationRef) all 按 id 筛选;未命中返回错误
ListConnectedProviders(ctx) 返回 connected(已连接 provider id 列表;不带 LocationRef,model.go:64)
权限应答 — permission.go

V1 的权限接口是全局的;ListPermissions 拉全量后按 sessionID 过滤。

Go API HTTP 说明
ListPermissions(ctx, sessionID) GET /permission 全局 pending 列表,客户端过滤
ReplyPermission(ctx, rid, reply, message) POST /permission/{rid}/reply reply ∈ once/always/reject
问题应答 — question.go
Go API HTTP 说明
ListQuestions(ctx, sessionID) GET /question 全局 pending 列表,客户端过滤
ReplyQuestion(ctx, rid, *QuestionReply) POST /question/{rid}/reply answers 与 questions 一一对应
RejectQuestion(ctx, rid) POST /question/{rid}/reject
非目标(明确未实现)
  • agent/model 切换(V1 无独立 Switch 接口,随 Prompt body 指定)
  • 主动创建权限请求(权限请求由服务端经事件推送)
  • session 级 SSE(V1 仅全局 /event,无 after 续传)
  • v2 全部 /api/* 端点
  • fs / pty / lsp / mcp / integration / credential / tui / sync / vcs / worktree / workspace 等 spec 中存在但不在 scope 的接口
  • 事件强类型 union(仅 scope 内高频事件附 properties struct)

快速开始

package main

import (
	"context"
	"encoding/json"
	"fmt"

	oc "github.com/justphantom/opencode-go-sdk-lite"
)

func main() {
	client, err := oc.New("http://127.0.0.1:4096",
		oc.WithToken("your-token"),           // 可选;本地部署通常省略
	)
	if err != nil { panic(err) }

	ctx := context.Background()

	// 1. 列出可用模型
	models, _ := client.ListModels(ctx, &oc.LocationRef{Directory: "/repo"})
	fmt.Println("models:", len(models))

	// 2. 创建会话
	ses, err := client.CreateSession(ctx, &oc.CreateSessionReq{Directory: "/repo"})
	if err != nil { panic(err) }

	// 3. 订阅事件流(在 prompt 之前打开,避免丢帧;
	//    Location 必须与会话 directory 一致——事件总线按 directory 隔离)
	events, errc := client.SessionEvents(ctx, ses.ID, &oc.SessionEventsOpt{
		Location: &oc.LocationRef{Directory: "/repo"},
	})

	// 4. 发送消息;messageID/partID 由 SDK 生成并经 ack 回传
	go func() {
		_, _ = client.Prompt(ctx, ses.ID, &oc.PromptReq{
			Parts: []oc.PromptPart{{Type: "text", Text: "解释这个项目"}},
		})
	}()

	// 5. 消费事件直到 turn 结束
	for ev := range events {
		switch ev.Type {
		case oc.EventMessagePartDelta:
			var d oc.PartDeltaData
			_ = json.Unmarshal(ev.Properties, &d)
			fmt.Print(d.Delta)

		case oc.EventPermissionAsked:
			var d oc.PermissionAskedData
			_ = json.Unmarshal(ev.Properties, &d)
			// 自动放行一次
			_ = client.ReplyPermission(ctx, d.ID, oc.PermissionReplyOnce, "")

		case oc.EventQuestionAsked:
			var d oc.QuestionAskedData
			_ = json.Unmarshal(ev.Properties, &d)
			// 第一项作为默认答案
			ans := make([][]string, len(d.Questions))
			for i, q := range d.Questions {
				if len(q.Options) > 0 { ans[i] = []string{q.Options[0].Label} }
			}
			_ = client.ReplyQuestion(ctx, d.ID, &oc.QuestionReply{Answers: ans})

		case oc.EventMessagePartUpdated:
			// 实测:真实完成信号是 step-finish part 且 reason="stop"(session.idle 兜底)
			var d oc.PartUpdatedData
			_ = json.Unmarshal(ev.Properties, &d)
			if d.Part.Type == "step-finish" && d.Part.Reason == "stop" {
				fmt.Println("\n[done]")
				return
			}
		}
	}
	if err := <-errc; err != nil {
		fmt.Println("stream error:", err)
	}
}

重连策略

SessionEventsGlobalEventStream 均内置指数退避重连: BackoffMin 按 2^n 增长,封顶 BackoffMax;4xx(除 429)不可恢复。

events, errc := client.SessionEvents(ctx, ses.ID, &oc.SessionEventsOpt{
	BackoffMin:  200 * time.Millisecond,
	BackoffMax:  10 * time.Second,
	MaxAttempts: 10,                     // 0 = 无限
})

注意:全局 /event 不支持续传,断连窗口的事件会丢失。

全局事件流(GlobalEventStream)

GlobalEventStream 维护一条到 /event 的全局长连,按 sessionID 把事件路由给多个订阅者。 适合需要并发处理多个会话的宿主(HTTP 网关、机器人适配层等)。健壮性移植自 lark-bridge: 指数退避(100ms→5s,连接存活 <2s 视为 flapping 不重置退避)、心跳 watchdog(15s 无帧强制重连破半开 TCP)、 panic recover。

断连窗口的 delta 事件会丢失;终止事件(idle/error/deleted)保证送达。 loc 必须与目标会话 directory 一致(事件总线按 directory 隔离)。

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

stream, _ := client.NewGlobalEventStream(ctx, &oc.LocationRef{Directory: "/repo"})
defer stream.Close()

// 为任意 sessionID 订阅
ch := stream.Subscribe("ses_xxx")
defer stream.Unsubscribe("ses_xxx") // 或 Close 自动关闭

for ev := range ch {
    // ev.Type / ev.Properties 同 SessionEvents 的事件结构
}

高层 Run + HighEvent(推荐)

Run 把「创建/复用 session → 订阅全局流 → 发 prompt_async → 按 assistantMessageID 过滤 → 合成终止事件」打包, 过滤用的 assistantMessageID 在多轮(agent-loop)下跟随最新 step 的 messageID(每轮换 ID,不跟随会丢后续轮次事件)。 把原始事件归纳为 12 种 HighEventKind,channel close 前必有终止事件(result/error)。 首事件必为 HighEventPrompt(携带 SDK 生成的 user messageID 与 sessionID)。

loc := &oc.LocationRef{Directory: "/repo"}
stream, _ := client.NewGlobalEventStream(ctx, loc)
defer stream.Close()

out, err := client.Run(ctx, stream, oc.RunOptions{
	Prompt:   "解释这个项目",
	Location: loc,
	// SessionID 空则内部 CreateSession;Model/Agent 随本条消息生效
})
if err != nil { return err }

	for ev := range out {
		switch ev.Kind() {
		case oc.HighEventPrompt:
			fmt.Println("session:", ev.SessionID(), "user msg:", ev.MessageID())
		case oc.HighEventText:
			fmt.Print(ev.Text())
		case oc.HighEventThinking:
			// 推理增量(v0.2.1):实时累积到本地 strings.Builder
		case oc.HighEventThinkingDone:
			// v0.2.1:思考 part 终止帧,服务端整合的权威全文,可覆盖累积值
		case oc.HighEventToolUse:
			fmt.Printf("\n[tool: %s] %s\n", ev.ToolName(), ev.ToolInput())
		case oc.HighEventToolResult:
			if ev.IsToolError() { fmt.Println("  (failed)") }
		case oc.HighEventPermissionAsked:
			// agent 请求权限:ev.PermissionAsked() → ReplyPermission 应答(见下文)
		case oc.HighEventQuestionAsked:
			// agent 向用户提问:ev.QuestionAsked() → ReplyQuestion/RejectQuestion 应答
		case oc.HighEventTodoUpdated:
			// 会话级 todo 全量列表:ev.TodoUpdated().Todos(非终止,turn 继续)
		case oc.HighEventResult:
			// v0.3.0 完整 turn 报告:见下「完整 turn 报告」
			fmt.Printf("\n[done] in=%d out=%d reasoning=%d cost=%.4f model=%s\n",
				ev.InputTokens(), ev.OutputTokens(), ev.ReasoningTokens(),
				ev.Cost(), ev.ModelID())
			return
		case oc.HighEventError:
			return // 出错终止
		}
	}
}

完成信号是 step-finish part 且 reason="stop"(实测确认;session.idle 作兜底终止)。 HighEventResult 的结果文本优先取服务端落库文本(GetMessageFinalText,免疫 SSE 丢帧); 取不到或为空则回退 SSE 累积的 text delta(run.go:120-151)。

完整 turn 报告(v0.3.0)

HighEventResult 升级为完整 turn 报告,集中所有元数据。新增 Getter:

Getter 数据源 用途
Thinking() string 落库 ReasoningText 优先,回退 SSE 累积 turn 完整思考全文(多 step 拼接)
ReasoningTokens() int step-finish.tokens.reasoning 本次 reasoning token 用量
ModelID() string / ProviderID() string SSE message.updated 主,GetMessage 兜底 本次回复所用 model(message 级)
SessionTokens() SessionTokens GetSession 会话累计 input/output/reasoning/cache
SessionCost() float64 GetSession 会话累计 cost
case oc.HighEventResult:
    fmt.Println("回复:", ev.Result())
    fmt.Println("思考:", ev.Thinking())
    fmt.Printf("model: %s / %s\n", ev.ModelID(), ev.ProviderID())
    fmt.Printf("本次: in=%d out=%d reasoning=%d cache={r:%d w:%d} cost=%.4f\n",
        ev.InputTokens(), ev.OutputTokens(), ev.ReasoningTokens(),
        ev.CacheRead(), ev.CacheWrite(), ev.Cost())
    st := ev.SessionTokens()
    fmt.Printf("会话累计: in=%.0f out=%.0f reasoning=%.0f cost=%.4f\n",
        st.Input, st.Output, st.Reasoning, ev.SessionCost())

每次 turn 结束额外发 1 次 GetSession RPC 拿累计用量;model 双源策略对齐 finalText/finalReasoning(落库优先,回退 SSE 累积)。

可观测性与 ctx 取消兜底(v0.2.0)

Logger 注入WithLogger(*slog.Logger) 默认 discardHandler(Go 1.22 无 slog.DiscardHandler)。覆盖 connect/dispatch/watchdog 等埋点;recoverPanic 记 stack;cancelConn 带 reason。

client, _ := oc.New(url,
    oc.WithLogger(slog.New(slog.NewTextHandler(os.Stderr, nil))),
)

双 watchdog:心跳 watchdog(15s,连接半开)+ 业务事件 watchdog(默认 5min,agent 卡死/服务端不 push)。后者不盲 cancel——触发 OnIdle 回调让订阅者决策;pendingAsked 状态跳过(等用户应答是主动挂起)。

stream.OnIdle = func(sid string, idleSince time.Time) {
    // 决策:GET messages 拉最终回复 / 主动 abort / 继续等
}
// 阈值可调:
client, _ := oc.New(url, oc.WithBusinessIdleTimeout(3*time.Minute))

ctx 取消不丢终止事件:pump 在 ctx.Done 时先 drainSrcOnExit 救 src 已缓冲事件,再 waitForTerminalInGrace(drainGrace 默认 500ms,可经 WithDrainGrace 调)等飞行中事件。RunWithHandle 暴露 WaitTerminal 让订阅者主动多等一段:

handle, _ := client.RunWithHandle(ctx, stream, opts)
// ...消费 handle.Events()...
// ctx 取消时:
ev, ok := handle.WaitTerminal(ctxWith2sTimeout)  // 接住飞行中的 HighEventResult

Run 签名保留(内部委托 RunWithHandle,返回 handle.Events())。详见 examples/observability/

权限/提问事件(permission_asked / question_asked)

agent 运行中请求权限(bash 等)或向用户提问时,Run 透出 HighEventPermissionAsked / HighEventQuestionAsked(非终止:chan 不 close,turn 挂起等应答)。消费模式:

  1. 收到 asked 事件,取 ev.PermissionAsked() / ev.QuestionAsked()(仅对应 kind 非 nil);
  2. ReplyPermission / ReplyQuestion / RejectQuestion 应答;
  3. 应答后 turn 继续流式输出。

不回复 serve 端 agent 会一直挂起——中断/超时务必回复 reject。 关联工具调用用 payload 的 Tool.CallID,不要靠时序:asked 相对对应 tool_use(running) 事件的先后不稳定(实测 permission.asked 在 running 之后、question.asked 在 running 之前)。

辅助 API

Go API 说明
client.Health(ctx) 健康检查;{healthy:true}
client.ListAgents(ctx, *LocationRef) 列出 agent(build/plan/explore...)
client.DeleteSession(ctx, id) 删除会话
GenerateMessageID() / GeneratePartID() 生成 msg_/prt_ 前缀 id,NTP 回拨安全;Prompt 内部自动调用,仅需要预关联时手动用
ClassifyTool(name) 工具名归类(file_read/file_write/shell/search/webfetch/mcp/subagent/todo/other)
NewHighEvent(...) 构造 HighEvent;仅供外部包测试 fake 用,业务代码不应调用
PromptAck Prompt 回执;prompt_async 返 204 无 body,ack 的 MessageID/PartIDs 是关联后续 SSE 事件的唯一句柄

约束

  • 零第三方依赖,仅标准库
  • 原始事件:Type 常量 + Properties json.RawMessage(不做 88 事件强类型 union,仅高频事件附 *Data struct)
  • 高层事件:HighEventKind 13 种 + Getter(封装在 Run
  • 全局流不支持续传,断连窗口事件丢失(v0.2.0 logger + 双 watchdog + drain 已大幅降低静默故障风险)
  • 其他未覆盖接口见「接口清单 → 非目标」

版本对照

版本 关键能力
v0.1.x 基础:对话/SSE/模型/CRUD + HighEvent 12 kind
v0.2.0 SSE 静默故障根治:WithLogger + 双 watchdog + drainSrcOnExit + RunWithHandle.WaitTerminal
v0.2.1 reasoning 完整文本:HighEventThinkingDone 终止帧 + HighEventResult.Thinking()
v0.3.0 HighEventResult 升级为完整 turn 报告:ReasoningTokens/ModelID/ProviderID/SessionTokens/SessionCost

Documentation

Index

Constants

View Source
const (
	PermissionReplyOnce   = "once"
	PermissionReplyAlways = "always"
	PermissionReplyReject = "reject"
)

PermissionReply 取值:once / always / reject。

View Source
const (
	EventCatalogUpdated            = "catalog.updated"
	EventCommandExecuted           = "command.executed"
	EventFileEdited                = "file.edited"
	EventFileWatcherUpdated        = "file.watcher.updated"
	EventGlobalDisposed            = "global.disposed"
	EventInstallationUpdateAvail   = "installation.update-available"
	EventInstallationUpdated       = "installation.updated"
	EventIntegrationConnUpdated    = "integration.connection.updated"
	EventIntegrationUpdated        = "integration.updated"
	EventLspUpdated                = "lsp.updated"
	EventMcpBrowserOpenFailed      = "mcp.browser.open.failed"
	EventMcpToolsChanged           = "mcp.tools.changed"
	EventMessagePartDelta          = "message.part.delta"
	EventMessagePartRemoved        = "message.part.removed"
	EventMessagePartUpdated        = "message.part.updated"
	EventMessageRemoved            = "message.removed"
	EventMessageUpdated            = "message.updated"
	EventModelsDevRefreshed        = "models-dev.refreshed"
	EventPermissionAsked           = "permission.asked"
	EventPermissionReplied         = "permission.replied"
	EventPluginAdded               = "plugin.added"
	EventProjectDirectoriesUpdated = "project.directories.updated"
	EventProjectUpdated            = "project.updated"
	EventPtyCreated                = "pty.created"
	EventPtyDeleted                = "pty.deleted"
	EventPtyExited                 = "pty.exited"
	EventPtyUpdated                = "pty.updated"
	EventQuestionAsked             = "question.asked"
	EventQuestionRejected          = "question.rejected"
	EventQuestionReplied           = "question.replied"
	EventReferenceUpdated          = "reference.updated"
	EventServerConnected           = "server.connected"
	EventSessionCompacted          = "session.compacted"
	EventSessionCreated            = "session.created"
	EventSessionDeleted            = "session.deleted"
	EventSessionDiff               = "session.diff"
	EventSessionError              = "session.error"
	EventSessionIdle               = "session.idle"
	EventSessionStatus             = "session.status"
	EventSessionUpdated            = "session.updated"
	EventTodoUpdated               = "todo.updated"
	EventTuiCommandExecute         = "tui.command.execute"
	EventTuiPromptAppend           = "tui.prompt.append"
	EventTuiSessionSelect          = "tui.session.select"
	EventTuiToastShow              = "tui.toast.show"
	EventVcsBranchUpdated          = "vcs.branch.updated"
	EventWorkspaceFailed           = "workspace.failed"
	EventWorkspaceReady            = "workspace.ready"
	EventWorkspaceStatus           = "workspace.status"
	EventWorktreeFailed            = "worktree.failed"
	EventWorktreeReady             = "worktree.ready"
)

Event Type 常量。覆盖服务端实测发出的事件(V1 经典事件体系, 实测不产生 session.next.* 与 *.v2.* 事件)。

View Source
const (
	PartTypeText       = "text"
	PartTypeReasoning  = "reasoning"
	PartTypeTool       = "tool"
	PartTypeStepStart  = "step-start"
	PartTypeStepFinish = "step-finish"
)

Part.Type 取值常量。散落在 highevent.go/run.go/types.go 的判断里, 提常量避免拼写漂移导致 part 路由失败。

Variables

This section is empty.

Functions

func GenerateMessageID

func GenerateMessageID() (string, error)

GenerateMessageID 生成一个新的 message id(msg_ 前缀)。

func GeneratePartID

func GeneratePartID() (string, error)

GeneratePartID 生成一个新的 part id(prt_ 前缀)。

Types

type APIError

type APIError struct {
	Status  int
	Type    string
	Message string
}

APIError 表示服务端返回的非 2xx 响应。

func (*APIError) Error

func (e *APIError) Error() string

type AgentInfo

type AgentInfo struct {
	Name        string          `json:"name"`
	Description string          `json:"description,omitempty"`
	Mode        string          `json:"mode"` // subagent | primary | all
	Native      bool            `json:"native,omitempty"`
	Hidden      bool            `json:"hidden"`
	Color       string          `json:"color,omitempty"`
	Steps       int             `json:"steps,omitempty"`
	Model       *ModelRef       `json:"-"`
	Variant     string          `json:"variant,omitempty"`
	Prompt      string          `json:"prompt,omitempty"`
	Permission  json.RawMessage `json:"permission,omitempty"`
	Options     json.RawMessage `json:"options,omitempty"`
}

AgentInfo 对应 V1 Agent schema;permission/options 保留 RawMessage 透传。 Model 已从 wire 的 modelID 键名归一到 ModelRef。

func (*AgentInfo) UnmarshalJSON

func (a *AgentInfo) UnmarshalJSON(data []byte) error

UnmarshalJSON 把 wire 的 model.{providerID,modelID} 归一到 ModelRef。

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client 是 opencode v1 HTTP API 的薄客户端。

func New

func New(baseURL string, opts ...Option) (*Client, error)

New 创建 Client。baseURL 形如 "http://127.0.0.1:4096"。

func (*Client) CreateSession

func (c *Client) CreateSession(ctx context.Context, req *CreateSessionReq) (*SessionInfo, error)

CreateSession 创建会话。req 留空时由服务端生成 id 并用默认 agent/model。

func (*Client) DeleteSession

func (c *Client) DeleteSession(ctx context.Context, sessionID string) error

DeleteSession 删除会话。

func (*Client) DeleteSessionIfIdle

func (c *Client) DeleteSessionIfIdle(ctx context.Context, sessionID string) error

DeleteSessionIfIdle 仅在会话非 busy 时删除。 busy 时拒绝且不发 DELETE;状态查询失败则透传错误,不降级强删。 注意:状态查询与删除之间存在竞态,仅为尽力而为的前置检查。

func (*Client) GetMessage

func (c *Client) GetMessage(ctx context.Context, sessionID, messageID string) (*SessionMessage, error)

GetMessage 返回单条消息(info + parts),用于终止后取服务端落库的最终回复。

func (*Client) GetProvider

func (c *Client) GetProvider(ctx context.Context, providerID string, loc *LocationRef) (*ProviderInfo, error)

GetProvider 返回单个 provider 详情。V1 无 /provider/{id},从 all 中按 id 筛选。

func (*Client) GetSession

func (c *Client) GetSession(ctx context.Context, sessionID string) (*SessionInfo, error)

GetSession 返回单个会话详情。

func (*Client) Health

func (c *Client) Health(ctx context.Context) error

Health 检查服务端是否可用。GET /global/health,解析 {healthy:true}, 响应非 2xx 或 healthy != true 都视为不健康。

func (*Client) Interrupt

func (c *Client) Interrupt(ctx context.Context, sessionID string) error

Interrupt 中断当前 agent-loop(POST /session/{id}/abort)。空闲时为 no-op。

func (*Client) ListAgents

func (c *Client) ListAgents(ctx context.Context, loc *LocationRef) ([]AgentInfo, error)

ListAgents 列出当前注册的 agent(build/plan/general/explore 等)。

func (*Client) ListChildren added in v0.1.1

func (c *Client) ListChildren(ctx context.Context, sessionID, directory string) ([]SessionInfo, error)

ListChildren 返回直接派生自指定会话的子 session(parentID=sessionID)。 directory 必须与父 Run 的 Location.Directory 一致:opencode serve 按 directory 隔离 session 存储,不带 directory 时 serve 在默认上下文查找,跨目录会 404。

用途:subagent(task 工具)在独立子 session 中运行,其 permission.asked / question.asked 事件的 sessionID 为子 sid,父 session 订阅者收不到。pump 用此 接口周期性发现子 session,额外订阅子 sid 以转发 asked 事件。

func (*Client) ListCommands

func (c *Client) ListCommands(ctx context.Context, loc *LocationRef) ([]CommandInfo, error)

ListCommands 列出可用命令。

func (*Client) ListConnectedProviders

func (c *Client) ListConnectedProviders(ctx context.Context) ([]string, error)

ListConnectedProviders 返回 serve 实际连接的 provider id 列表 (已配置凭证且可达);与 ListProviders 返回的全量目录互补, 调用方按它过滤才能得到"可跑"子集。Connected 是全局配置, 不受 LocationRef 影响,故不接受 loc 参数。

func (*Client) ListMessages

func (c *Client) ListMessages(ctx context.Context, sessionID string, opt *ListMessagesOpt) ([]SessionMessage, error)

ListMessages 列出会话历史消息(info + parts)。

func (*Client) ListModels

func (c *Client) ListModels(ctx context.Context, loc *LocationRef) ([]ModelInfo, error)

ListModels 列出所有 provider 下的模型。V1 无独立模型目录, 模型清单内嵌在 GET /provider 的 all[].models 中,此处拍平; Enabled 由 status=="active" 推导。

func (*Client) ListPermissions

func (c *Client) ListPermissions(ctx context.Context, sessionID string) ([]PermissionRequest, error)

ListPermissions 列出会话内挂起的权限请求。 V1 的 GET /permission 是全局 pending 列表,此处按 sessionID 过滤。

func (*Client) ListProviders

func (c *Client) ListProviders(ctx context.Context, loc *LocationRef) ([]ProviderInfo, error)

ListProviders 列出可用 provider。

func (*Client) ListQuestions

func (c *Client) ListQuestions(ctx context.Context, sessionID string) ([]QuestionRequest, error)

ListQuestions 列出会话内挂起的问题请求。 V1 的 GET /question 是全局 pending 列表,此处按 sessionID 过滤。

func (*Client) ListSessions

func (c *Client) ListSessions(ctx context.Context, opt *ListSessionsOpt) ([]SessionInfo, error)

ListSessions 列出 session。serve 无游标分页且默认 limit=100 会静默截断, SDK 默认上送 limit=200;会话数超过 200 需显式传更大 Limit。

func (*Client) ListSkills

func (c *Client) ListSkills(ctx context.Context, loc *LocationRef) ([]SkillInfo, error)

ListSkills 列出可用 skill。

func (*Client) ListTodos added in v0.0.8

func (c *Client) ListTodos(ctx context.Context, sessionID string) ([]Todo, error)

ListTodos 返回会话当前的 todo 全量列表(GET /session/{id}/todo)。 用作 todo.updated SSE 丢帧时的补偿恢复源;Todos 为全量覆盖列表,非增量。

func (*Client) NewGlobalEventStream

func (c *Client) NewGlobalEventStream(ctx context.Context, loc *LocationRef) (*GlobalEventStream, error)

NewGlobalEventStream 构造并启动后台 goroutine(reader + heartbeat watchdog)。 loc 定位事件总线(按 directory 隔离);nil 表示服务端默认目录。 调用方应在第一次 Prompt 前调用,避免丢首帧。Close 即停止后台。

func (*Client) Prompt

func (c *Client) Prompt(ctx context.Context, sessionID string, req *PromptReq) (*PromptAck, error)

Prompt 异步发送一条消息并调度 agent-loop(POST /session/{id}/prompt_async)。 服务端返 204 无 body:没有 admitted 确认,messageID/partID 由 SDK 生成 (调用方显式传入且前缀合法时尊重原值),经 PromptAck 回传, 用于关联后续 SSE 事件。agent/model 随本条消息生效(V1 无独立的 Switch 接口)。

func (*Client) RejectQuestion

func (c *Client) RejectQuestion(ctx context.Context, requestID, directory string) error

RejectQuestion 拒绝一条挂起的问题请求。directory 同 ReplyQuestion。

func (*Client) ReplyPermission

func (c *Client) ReplyPermission(ctx context.Context, requestID, directory, reply, message string) error

ReplyPermission 回复一条挂起的权限请求。 reply 取值 once / always / reject;message 可选,附在回复上。 directory 必须与该 permission 所在 Run 的 Location 一致:opencode serve 按 directory 隔离 pending permission,不带 directory 时 serve 返回 404。

func (*Client) ReplyQuestion

func (c *Client) ReplyQuestion(ctx context.Context, requestID, directory string, r *QuestionReply) error

ReplyQuestion 回答一条挂起的问题请求。answers 与 questions 一一对应。 directory 必须与该 question 所在 Run 的 Location 一致:opencode serve 按 directory 隔离 pending question,不带 directory 时 serve 在默认上下文找不到 请求并返回 404。

func (*Client) Run

func (c *Client) Run(ctx context.Context, stream *GlobalEventStream, opts RunOptions) (<-chan HighEvent, error)

Run 执行一轮对话:建/复用 session → 订阅全局流 → 发 prompt_async → 按 assistantMessageID 过滤 → 合成终止事件 → close chan。

首事件必为 HighEventPrompt(携带 sessionID + user messageID)。 channel close 前必有 HighEventResult 或 HighEventError(除非 ctx 取消)。

stream 必须是已启动的 GlobalEventStream;Run 会 Subscribe(sessionID) 后 Unsubscribe。 Agent/Model 随本条消息生效(V1 无 Switch 接口)。

等价于 RunWithHandle(...).Events()——如需订阅者 ctx 取消后主动等终止事件,请改用 RunWithHandle。

func (*Client) RunWithHandle added in v0.2.0

func (c *Client) RunWithHandle(ctx context.Context, stream *GlobalEventStream, opts RunOptions) (*RunHandle, error)

RunWithHandle 与 Run 等价,但返回 *RunHandle(额外暴露 WaitTerminal)。 bridge / 长链路订阅者建议改用本接口,ctx 取消后用 handle.WaitTerminal 多等一段, 接住飞行中的终止事件(配合 pump 内部 drainSrcOnExit + drainGrace 双层兜底)。

func (*Client) SessionEvents

func (c *Client) SessionEvents(ctx context.Context, sessionID string, opt *SessionEventsOpt) (<-chan Event, <-chan error)

SessionEvents 订阅会话级事件流,返回事件 chan 与错误 chan。 V1 无会话级 SSE 端点,实际连接全局 GET /event 后按 sessionID 过滤; 全局流不支持 after 续传,断连窗口的事件会丢失。 内部循环:连接 → 解析 → 写 chan → 断线指数退避重连。 不可恢复的 HTTP 错误(4xx,除 429)写 errc 后停止。ctx 取消即关闭 chan。

调用方典型用法:

events, errc := client.SessionEvents(ctx, id, nil)
for ev := range events {
    switch ev.Type {
    case opencode.EventSessionNextTextDelta: ...
    case opencode.EventSessionIdle: return
    }
}
if err := <-errc; err != nil { ... }

func (*Client) SessionStatuses

func (c *Client) SessionStatuses(ctx context.Context) (map[string]SessionStatus, error)

SessionStatuses 返回所有会话的运行状态(GET /session/status)。 键为 sessionID;空闲会话可能缺省,缺省即 idle。

func (*Client) UpdateSession

func (c *Client) UpdateSession(ctx context.Context, sessionID string, req *UpdateSessionReq) (*SessionInfo, error)

UpdateSession 更新会话标题/元数据/归档时间,返回更新后的会话。

type CommandInfo

type CommandInfo struct {
	Name        string   `json:"name"`
	Description string   `json:"description,omitempty"`
	Agent       string   `json:"agent,omitempty"`
	Model       string   `json:"model,omitempty"`
	Source      string   `json:"source,omitempty"`
	Template    string   `json:"template"`
	Subtask     bool     `json:"subtask,omitempty"`
	Hints       []string `json:"hints"`
}

CommandInfo 对应 GET /command 响应元素。 Source 取值:command(自定义命令)/ mcp / skill。

type CreateSessionReq

type CreateSessionReq struct {
	ParentID    string           `json:"parentID,omitempty"`
	Title       string           `json:"title,omitempty"`
	Agent       string           `json:"agent,omitempty"`
	Model       *ModelRef        `json:"model,omitempty"`
	Metadata    map[string]any   `json:"metadata,omitempty"`
	Permission  []PermissionRule `json:"permission,omitempty"`
	Directory   string           `json:"-"`
	WorkspaceID string           `json:"workspaceID,omitempty"`
}

CreateSessionReq 对应 POST /session;Directory/WorkspaceID 走平铺 query,其余进 body。

type Event

type Event struct {
	ID         string          `json:"id"`
	Type       string          `json:"type"`
	Properties json.RawMessage `json:"properties,omitempty"`
}

Event 是 SSE 推送的一条事件。实测 envelope 的数据字段是 properties (不是 spec 写的 data),保留为原始 JSON,由调用方按 Type 反序列化。

type GlobalEventStream

type GlobalEventStream struct {

	// OnIdle 业务事件空闲回调(默认 nil)。空闲超 businessIdleTimeout 触发,
	// 在锁外执行;pendingAsked 状态跳过。回调 panic 由独立 recover 兜底。
	OnIdle func(sessionID string, idleSince time.Time)
	// contains filtered or unexported fields
}

GlobalEventStream 维护一条到 /event 的全局长连,按 sessionID 路由事件给订阅者。 事件总线按 directory 隔离(实测):loc 必须与目标会话的 directory 一致, 否则收不到这些会话的事件。 设计要点(移植自 lark-bridge/internal/opencodeserve/stream.go,已验证):

  • 指数退避 100ms→5s,连接存活 <2s 视为 flapping 不重置退避
  • 心跳 watchdog 15s 无帧则强制重连破半开 TCP
  • panic recover 不让 goroutine 崩溃传播
  • 终止事件(session.idle/session.error/session.deleted)必送达,非终止满则丢

注意:全局流不支持续传,断连窗口的 delta 事件会丢失。

func (*GlobalEventStream) Close

func (s *GlobalEventStream) Close() error

Close 停止后台 goroutine,关闭所有订阅 chan。幂等。

func (*GlobalEventStream) Subscribe

func (s *GlobalEventStream) Subscribe(sessionID string) <-chan Event

Subscribe 注册 sessionID 订阅,返回事件 chan。 同一 sessionID 重复 Subscribe:关闭旧 chan 再建新的(订阅语义对齐 lark-bridge)。 chan 在 Unsubscribe 或 Close 时关闭。

func (*GlobalEventStream) Unsubscribe

func (s *GlobalEventStream) Unsubscribe(sessionID string)

Unsubscribe 取消订阅并关闭 chan。幂等。

type HighEvent

type HighEvent struct {
	// contains filtered or unexported fields
}

HighEvent 是 Run 对外暴露的高层事件。字段非导出,用 Getter 访问, 对齐 lark-bridge 接入约定(bridge 零转换接入)。

func (HighEvent) CacheRead

func (e HighEvent) CacheRead() int

func (HighEvent) CacheWrite

func (e HighEvent) CacheWrite() int

func (HighEvent) Cost

func (e HighEvent) Cost() float64

func (HighEvent) InputTokens

func (e HighEvent) InputTokens() int

func (HighEvent) IsError

func (e HighEvent) IsError() bool

func (HighEvent) IsToolError

func (e HighEvent) IsToolError() bool

func (HighEvent) Kind

func (e HighEvent) Kind() HighEventKind

Getter

func (HighEvent) MessageID

func (e HighEvent) MessageID() string

func (HighEvent) ModelID added in v0.3.0

func (e HighEvent) ModelID() string

ModelID / ProviderID 仅 HighEventResult 携带本次回复所用 model(message 级)。 双源:SSE message.updated 优先,空则 GetMessage 兜底。

func (HighEvent) OutputTokens

func (e HighEvent) OutputTokens() int

func (HighEvent) PermissionAsked added in v0.0.2

func (e HighEvent) PermissionAsked() *PermissionAskedData

PermissionAsked 仅 kind==HighEventPermissionAsked 时非 nil,其余 kind 返回 nil。

func (HighEvent) ProviderID added in v0.3.0

func (e HighEvent) ProviderID() string

func (HighEvent) QuestionAsked added in v0.0.2

func (e HighEvent) QuestionAsked() *QuestionAskedData

QuestionAsked 仅 kind==HighEventQuestionAsked 时非 nil,其余 kind 返回 nil。

func (HighEvent) ReasoningTokens added in v0.3.0

func (e HighEvent) ReasoningTokens() int

ReasoningTokens 仅 HighEventResult 携带本次 reasoning token 用量(来自 step-finish)。

func (HighEvent) Result

func (e HighEvent) Result() string

func (HighEvent) SessionCost added in v0.3.0

func (e HighEvent) SessionCost() float64

func (HighEvent) SessionID

func (e HighEvent) SessionID() string

func (HighEvent) SessionTokens added in v0.3.0

func (e HighEvent) SessionTokens() SessionTokens

SessionTokens / SessionCost 仅 HighEventResult 携带会话累计用量(调 GetSession)。

func (HighEvent) Text

func (e HighEvent) Text() string

func (HighEvent) Thinking added in v0.2.1

func (e HighEvent) Thinking() string

Thinking 仅 HighEventResult 携带 turn 完整思考全文(落库优先,回退 SSE 累积)。 其余 kind 返回 ""(增量思考请用 Text() 配合 HighEventThinking/HighEventThinkingDone)。

func (HighEvent) TodoUpdated added in v0.0.8

func (e HighEvent) TodoUpdated() *TodoUpdatedData

TodoUpdated 仅 kind==HighEventTodoUpdated 时非 nil,其余 kind 返回 nil。

func (HighEvent) ToolInput

func (e HighEvent) ToolInput() string

func (HighEvent) ToolKind

func (e HighEvent) ToolKind() ToolKind

func (HighEvent) ToolName

func (e HighEvent) ToolName() string

type HighEventKind

type HighEventKind string

HighEventKind 是高层事件的语义类别(12 种)。 不同于原始 Event(V1 经典事件体系),HighEvent 把过程流归纳为少数可消费类别。

const (
	HighEventPrompt     HighEventKind = "prompt"      // Run 首事件,携带 user messageID
	HighEventText       HighEventKind = "text"        // 文本增量
	HighEventThinking   HighEventKind = "thinking"    // 思考增量
	HighEventToolUse    HighEventKind = "tool_use"    // 工具调用发起
	HighEventToolResult HighEventKind = "tool_result" // 工具调用结果
	HighEventStepStart  HighEventKind = "step_start"
	HighEventStepFinish HighEventKind = "step_finish"
	HighEventResult     HighEventKind = "result" // 终止-成功
	HighEventError      HighEventKind = "error"  // 终止-失败

	// asked 两个事件均为非终止:agent 挂起等用户应答,应答后 turn 继续。
	HighEventPermissionAsked HighEventKind = "permission_asked"
	HighEventQuestionAsked   HighEventKind = "question_asked"

	HighEventTodoUpdated HighEventKind = "todo_updated" // 会话级 todo 全量列表更新

	// HighEventThinkingDone 思考 part 终止帧:part.updated{type=reasoning text!=""}。
	// 携带服务端整合后的完整文本,调用方可据此覆盖累积值(权威)。
	// 非终止(turn 继续);HighEventResult.Thinking() 也回填累积的思考全文。
	HighEventThinkingDone HighEventKind = "thinking_done"
)

type ListMessagesOpt

type ListMessagesOpt struct {
	Directory string
	Workspace string
	Limit     int
	Before    string
}

ListMessagesOpt 是 GET /session/{id}/message 的查询参数。

type ListSessionsOpt

type ListSessionsOpt struct {
	Directory string
	Workspace string
	Scope     string
	Search    string
	Limit     int // <=0 时用 defaultListSessionsLimit
}

ListSessionsOpt 是 GET /session 的查询参数。

type LocationRef

type LocationRef struct {
	Directory   string `json:"directory"`
	WorkspaceID string `json:"workspaceID,omitempty"`
}

LocationRef 定位一个工作区目录;至少给出 Directory。 V1 接口以平铺 query(directory/workspace)传递,见 locationQuery。

type MessageInfo

type MessageInfo struct {
	ID         string        `json:"id"`
	SessionID  string        `json:"sessionID"`
	Role       string        `json:"role"`
	Agent      string        `json:"agent,omitempty"`
	Finish     string        `json:"finish,omitempty"`
	Cost       float64       `json:"cost,omitempty"`
	Tokens     SessionTokens `json:"tokens,omitempty"`
	ModelID    string        `json:"modelID,omitempty"`    // assistant 消息所用模型(实测服务端返回)
	ProviderID string        `json:"providerID,omitempty"` // 对应 provider
}

MessageInfo 是 User/Assistant 消息的公共字段(assistant 专有字段在 user 消息上为零值)。 更多字段(parts 之外的)请按 role 自行反序列化 Parts。

type MessageUpdatedData

type MessageUpdatedData struct {
	SessionID string      `json:"sessionID"`
	Info      MessageInfo `json:"info"`
}

MessageUpdatedData 是 message.updated 的 properties。

type ModelAPI

type ModelAPI struct {
	ID  string `json:"id"`
	URL string `json:"url"`
	NPM string `json:"npm"`
}

ModelAPI 是 provider 的 API 接入信息。

type ModelCapabilities

type ModelCapabilities struct {
	Temperature bool            `json:"temperature,omitempty"`
	Reasoning   bool            `json:"reasoning,omitempty"`
	Attachment  bool            `json:"attachment,omitempty"`
	Toolcall    bool            `json:"toolcall"`
	Input       map[string]bool `json:"input,omitempty"`
	Output      map[string]bool `json:"output,omitempty"`
}

ModelCapabilities 按服务端实测结构(与 spec 声明不同): input/output 是模态→布尔的对象,工具能力键为 toolcall。

type ModelCost

type ModelCost struct {
	Input  float64 `json:"input"`
	Output float64 `json:"output"`
	Cache  struct {
		Read  float64 `json:"read"`
		Write float64 `json:"write"`
	} `json:"cache"`
}

ModelCost 是模型的单次计费(按 token 拆分,含缓存)。

type ModelInfo

type ModelInfo struct {
	ID           string            `json:"id"`
	ProviderID   string            `json:"providerID"`
	Name         string            `json:"name"`
	Family       string            `json:"family,omitempty"`
	Status       string            `json:"status"`
	API          ModelAPI          `json:"api"`
	Capabilities ModelCapabilities `json:"capabilities"`
	Cost         ModelCost         `json:"cost"`
	Limit        ModelLimit        `json:"limit"`
	Options      map[string]any    `json:"options,omitempty"`
	Headers      map[string]string `json:"headers,omitempty"`
	ReleaseDate  string            `json:"release_date,omitempty"`
	Variants     json.RawMessage   `json:"variants,omitempty"`
	Enabled      bool              `json:"-"`
}

ModelInfo 对应 V1 Model schema;Enabled 由 status=="active" 推导(见 ListModels)。

type ModelLimit

type ModelLimit struct {
	Context int `json:"context"`
	Input   int `json:"input,omitempty"`
	Output  int `json:"output"`
}

ModelLimit 是模型的上下文/输入/输出 token 上限。

type ModelRef

type ModelRef struct {
	ID         string `json:"id"`
	ProviderID string `json:"providerID"`
	Variant    string `json:"variant,omitempty"`
}

ModelRef 引用一个 provider 模型;与 V1 Session.model 同构。

type Option

type Option func(*Client)

Option 配置 Client。

func WithBasicAuth

func WithBasicAuth(user, pass string) Option

WithBasicAuth 设置 HTTP Basic 认证。与 WithToken 互斥,后应用者生效。

func WithBusinessIdleTimeout added in v0.2.0

func WithBusinessIdleTimeout(d time.Duration) Option

WithBusinessIdleTimeout 设业务事件空闲阈值。0 或不调 = 用包级默认(5min)。 订阅者可按业务特性收紧/放宽(如 CI agent 长任务可放宽,ChatOps 短任务可收紧)。

func WithDrainGrace added in v0.2.0

func WithDrainGrace(d time.Duration) Option

WithDrainGrace 设 pump ctx 取消后等待飞行中终止事件的宽限时间。 0 或不调 = 用包级默认(500ms);负值禁用宽限(pump 立即走合成 HighEventError 路径)。

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient 注入自定义 *http.Client。

func WithHeader

func WithHeader(key, value string) Option

WithHeader 追加/覆盖单个请求头。

func WithLogger added in v0.2.0

func WithLogger(l *slog.Logger) Option

WithLogger 注入 logger,覆盖 connect/dispatch/watchdog 等内部埋点。 默认 newDefaultLogger()(New 里兜底),零调用方感知。

func WithPassword

func WithPassword(pass string) Option

WithPassword 以 opencode serve 密码模式登录(Basic 认证,用户名固定 "opencode", 密码即服务端 OPENCODE_SERVER_PASSWORD)。

func WithToken

func WithToken(token string) Option

WithToken 设置 Authorization: Bearer <token>。

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent 便捷设置 User-Agent。

type Part

type Part struct {
	ID        string     `json:"id"`
	MessageID string     `json:"messageID"`
	SessionID string     `json:"sessionID"`
	Type      string     `json:"type"`
	Text      string     `json:"text,omitempty"`
	Synthetic bool       `json:"synthetic,omitempty"` // text part:服务端合成,不计入最终回复
	Ignored   bool       `json:"ignored,omitempty"`   // text part:被忽略,不计入最终回复
	Reason    string     `json:"reason,omitempty"`    // step-finish 的终止原因,"stop" 为成功
	Tool      string     `json:"tool,omitempty"`
	CallID    string     `json:"callID,omitempty"`
	State     *ToolState `json:"state,omitempty"`
	Tokens    StepTokens `json:"tokens,omitempty"`
	Cost      float64    `json:"cost,omitempty"`
}

Part 是消息的一个组成块。type 取值:text / reasoning / tool / step-start / step-finish 等。tool 专有字段在 State。

type PartDeltaData

type PartDeltaData struct {
	SessionID string `json:"sessionID"`
	MessageID string `json:"messageID"`
	PartID    string `json:"partID"`
	Field     string `json:"field"`
	Delta     string `json:"delta"`
}

PartDeltaData 是 message.part.delta 的 properties。 field 恒为 "text";part 是 text 还是 reasoning 需结合 partID 查 message.part.updated 中的 part.type(SDK 内部已做,见 mapToHighEvent)。

type PartUpdatedData

type PartUpdatedData struct {
	SessionID string `json:"sessionID"`
	Part      Part   `json:"part"`
	Time      int64  `json:"time"`
}

PartUpdatedData 是 message.part.updated 的 properties。

type PermissionAskedData

type PermissionAskedData struct {
	ID         string         `json:"id"`
	SessionID  string         `json:"sessionID"`
	Permission string         `json:"permission"`
	Patterns   []string       `json:"patterns,omitempty"`
	Metadata   map[string]any `json:"metadata,omitempty"`
	Always     []string       `json:"always,omitempty"`
	// 实测 serve 会发 tool 字段(关联发起权限请求的工具调用),spec 未声明。
	Tool *PermissionTool `json:"tool,omitempty"`
}

PermissionAskedData 是 permission.asked 的 data;与 PermissionRequest 同构。

type PermissionRequest

type PermissionRequest struct {
	ID         string          `json:"id"`
	SessionID  string          `json:"sessionID"`
	Permission string          `json:"permission"`
	Patterns   []string        `json:"patterns,omitempty"`
	Metadata   map[string]any  `json:"metadata,omitempty"`
	Always     []string        `json:"always,omitempty"`
	Tool       *PermissionTool `json:"tool,omitempty"`
}

PermissionRequest 对应 V1 PermissionRequest schema。

type PermissionRule

type PermissionRule struct {
	Permission string `json:"permission"`
	Pattern    string `json:"pattern"`
	Action     string `json:"action"`
}

PermissionRule 对应 V1 PermissionRule schema;Action 取值 allow / deny / ask。

type PermissionTool

type PermissionTool struct {
	MessageID string `json:"messageID"`
	CallID    string `json:"callID"`
}

PermissionTool 标记权限请求归属的工具调用。

type PromptAck

type PromptAck struct {
	MessageID string
	PartIDs   []string
}

PromptAck 是 Prompt 的回执:prompt_async 返 204 无 body, messageID/partID 是关联后续 SSE 事件(message.updated、message.part.*)的唯一句柄。

type PromptModelRef

type PromptModelRef struct {
	ProviderID string `json:"providerID"`
	ModelID    string `json:"modelID"`
}

PromptModelRef 是 GET /agent 响应中 Agent.model 的模型引用(注意 wire 字段是 modelID)。 prompt 请求侧统一用 ModelRef,由 SDK 内部转换(见 Client.Prompt)。

type PromptPart

type PromptPart struct {
	ID       string `json:"id,omitempty"`
	Type     string `json:"type"`
	Text     string `json:"text,omitempty"`
	Mime     string `json:"mime,omitempty"`
	Filename string `json:"filename,omitempty"`
	URL      string `json:"url,omitempty"`
}

PromptPart 是 prompt_async parts 的元素。 text 类型填 Text;file 类型(附件)填 Mime/URL(Filename 可选)。 ID 留空时由 SDK 生成(prt_ 前缀),见 Client.Prompt。

type PromptReq

type PromptReq struct {
	MessageID string          `json:"-"`
	Model     *ModelRef       `json:"-"`
	Agent     string          `json:"agent,omitempty"`
	NoReply   bool            `json:"noReply,omitempty"`
	System    string          `json:"system,omitempty"`
	Variant   string          `json:"variant,omitempty"`
	Tools     map[string]bool `json:"tools,omitempty"`
	Parts     []PromptPart    `json:"parts"`
}

PromptReq 对应 POST /session/{id}/prompt_async 的请求体。 MessageID 留空时由 SDK 生成(msg_ 前缀),生成结果经 PromptAck 回传。 Tools 是工具开关(工具名 → 是否启用),见 spec message body.tools。

type ProviderInfo

type ProviderInfo struct {
	ID      string               `json:"id"`
	Name    string               `json:"name"`
	Source  string               `json:"source"`
	Env     []string             `json:"env,omitempty"`
	Options map[string]any       `json:"options,omitempty"`
	Models  map[string]ModelInfo `json:"models,omitempty"`
}

ProviderInfo 对应 V1 Provider schema;Models 以 modelID 为键。

type QuestionAskedData

type QuestionAskedData struct {
	ID        string         `json:"id"`
	SessionID string         `json:"sessionID"`
	Questions []QuestionInfo `json:"questions"`
	Tool      *QuestionTool  `json:"tool,omitempty"`
}

QuestionAskedData 是 question.asked 的 data;与 QuestionRequest 同构。

type QuestionInfo

type QuestionInfo struct {
	Question string           `json:"question"`
	Header   string           `json:"header"`
	Options  []QuestionOption `json:"options"`
	Multiple bool             `json:"multiple,omitempty"`
	Custom   bool             `json:"custom,omitempty"`
}

QuestionInfo 是单个问题的结构(question.asked 的元素)。

type QuestionOption

type QuestionOption struct {
	Label       string `json:"label"`
	Description string `json:"description"`
}

QuestionOption 是问题的候选项。

type QuestionReply

type QuestionReply struct {
	Answers [][]string `json:"answers"`
}

QuestionReply 对应 question.reply 的 body;Answers 与 questions 一一对应, 每个元素是该问题的选中 label 列表。

type QuestionRequest

type QuestionRequest struct {
	ID        string         `json:"id"`
	SessionID string         `json:"sessionID"`
	Questions []QuestionInfo `json:"questions"`
	Tool      *QuestionTool  `json:"tool,omitempty"`
}

QuestionRequest 对应 V1 QuestionRequest schema。

type QuestionTool

type QuestionTool struct {
	MessageID string `json:"messageID"`
	CallID    string `json:"callID"`
}

QuestionTool 标记问题请求归属的工具调用。

type RevertState

type RevertState struct {
	MessageID string `json:"messageID,omitempty"`
	PartID    string `json:"partID,omitempty"`
	Snapshot  string `json:"snapshot,omitempty"`
	Diff      string `json:"diff,omitempty"`
}

RevertState 对应 V1 Session.revert。

type RunHandle added in v0.2.0

type RunHandle struct {
	// contains filtered or unexported fields
}

RunHandle 封装 Run 的输出 chan 与主动等终止事件的能力。 订阅者 ctx 取消后,调 WaitTerminal 显式多等一段以接住飞行中的终止事件 (配合 pump 内部 drainSrcOnExit + drainGrace 双层兜底)。

func (*RunHandle) Events added in v0.2.0

func (h *RunHandle) Events() <-chan HighEvent

Events 返回事件 chan,可直接 for-range(兼容旧 Run 调用形态)。

func (*RunHandle) WaitTerminal added in v0.2.0

func (h *RunHandle) WaitTerminal(ctx context.Context) (HighEvent, bool)

WaitTerminal 阻塞等终止事件(HighEventResult / HighEventError)或 ctx 取消/chan close。 用于订阅者 ctx 取消后,明确"再多等 N 时长"以接住飞行中的终止事件。 返回 (事件, true) 表示取到终止事件;返回 (零值, false) 表示 ctx 超时或 chan 已 close。

type RunOptions

type RunOptions struct {
	Prompt    string
	SessionID string
	Model     *ModelRef
	Agent     string
	Location  *LocationRef // directory 在此
}

RunOptions 是 Run 的高层参数。SessionID 空则内部 CreateSession。

type SessionCache

type SessionCache struct {
	Read  float64 `json:"read"`
	Write float64 `json:"write"`
}

SessionCache 是 token 用量的缓存命中部分。

type SessionErrorData

type SessionErrorData struct {
	SessionID string         `json:"sessionID"`
	Error     map[string]any `json:"error"`
}

SessionErrorData 是 session.error 的 data;Error 字段至少含 message。

type SessionEventsOpt

type SessionEventsOpt struct {
	// Location 定位工作区。事件总线按 directory 隔离(实测):不带 directory
	// 的连接收不到其他 directory 下会话的事件。必须与目标 session 的 directory 一致。
	Location *LocationRef
	// BackoffMin / BackoffMax 限制指数退避区间。零值走默认。
	BackoffMin time.Duration
	BackoffMax time.Duration
	// MaxAttempts 重连尝试上限;0 表示无限重试。
	MaxAttempts int
}

SessionEventsOpt 配置 SessionEvents 订阅。

type SessionIdleData

type SessionIdleData struct {
	SessionID string `json:"sessionID"`
}

SessionIdleData 是 session.idle 的 data;turn 结束兜底信号。

type SessionInfo

type SessionInfo struct {
	ID          string          `json:"id"`
	Slug        string          `json:"slug,omitempty"`
	ProjectID   string          `json:"projectID"`
	WorkspaceID string          `json:"workspaceID,omitempty"`
	Directory   string          `json:"directory"`
	Path        string          `json:"path,omitempty"`
	ParentID    string          `json:"parentID,omitempty"`
	Title       string          `json:"title"`
	Agent       string          `json:"agent,omitempty"`
	Model       *ModelRef       `json:"model,omitempty"`
	Version     string          `json:"version,omitempty"`
	Cost        float64         `json:"cost"`
	Tokens      SessionTokens   `json:"tokens"`
	Time        SessionTime     `json:"time"`
	Summary     *SessionSummary `json:"summary,omitempty"`
	Share       *SessionShare   `json:"share,omitempty"`
	Metadata    map[string]any  `json:"metadata,omitempty"`
	Permission  json.RawMessage `json:"permission,omitempty"`
	Revert      *RevertState    `json:"revert,omitempty"`
}

SessionInfo 对应 V1 Session schema。

type SessionMessage

type SessionMessage struct {
	Info  MessageInfo       `json:"info"`
	Parts []json.RawMessage `json:"parts"`
}

SessionMessage 是 GET /session/{id}/message 的元素:消息元信息 + parts。 Parts 保留原始 JSON(Part 有 10+ 种类型),调用方按需反序列化。

func (SessionMessage) FinalText

func (m SessionMessage) FinalText() string

FinalText 组装消息的最终回复文本:拼接 type=="text" 且非 synthetic/ignored 的 part.text;解析失败的 part 跳过。 SSE 断连后用它从 ListMessages 历史重组最终回复(兜底)。

func (SessionMessage) ReasoningText added in v0.0.6

func (m SessionMessage) ReasoningText() string

ReasoningText 组装消息的完整思考内容:拼接 type=="reasoning" 的 part.text,多段以 "\n" 分隔(实测一条 message 通常仅一段)。 解析失败/空文本 part 跳过;服务端未落库思考时返回 ""。

type SessionShare

type SessionShare struct {
	URL string `json:"url"`
}

SessionShare 是 session 的分享链接。

type SessionStatus

type SessionStatus struct {
	Type string `json:"type"` // idle | busy | retry
}

SessionStatus 是 GET /session/status 的单会话状态。 不在返回 map 中的会话视为 idle。

type SessionSummary

type SessionSummary struct {
	Additions float64 `json:"additions"`
	Deletions float64 `json:"deletions"`
	Files     float64 `json:"files"`
}

SessionSummary 是 session 的代码改动统计。

type SessionTime

type SessionTime struct {
	Created    int64 `json:"created"`
	Updated    int64 `json:"updated"`
	Compacting int64 `json:"compacting,omitempty"`
	Archived   int64 `json:"archived,omitempty"`
}

SessionTime 的时间戳为毫秒整数。

type SessionTokens

type SessionTokens struct {
	Input     float64      `json:"input"`
	Output    float64      `json:"output"`
	Reasoning float64      `json:"reasoning"`
	Cache     SessionCache `json:"cache"`
}

SessionTokens 是 Session/Message 的 token 用量统计。

type SkillInfo

type SkillInfo struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Location    string `json:"location"`
	Content     string `json:"content"`
}

SkillInfo 对应 GET /skill 响应元素;Content 为 skill 全文(含 frontmatter)。

type StepCache

type StepCache struct {
	Read  float64 `json:"read"`
	Write float64 `json:"write"`
}

StepCache 是 step 级 token 用量的缓存命中部分。

type StepTokens

type StepTokens struct {
	Input     float64   `json:"input"`
	Output    float64   `json:"output"`
	Reasoning float64   `json:"reasoning"`
	Cache     StepCache `json:"cache"`
}

StepTokens 是单个 step 的 token 用量(step.ended 携带)。

type Todo

type Todo struct {
	Content  string `json:"content"`
	Status   string `json:"status"` // pending | in_progress | completed | cancelled
	Priority string `json:"priority"`
}

Todo 对应 V1 Todo schema。

type TodoUpdatedData

type TodoUpdatedData struct {
	SessionID string `json:"sessionID"`
	Todos     []Todo `json:"todos"`
}

TodoUpdatedData 是 todo.updated 的 properties;Todos 为该会话当前完整列表。

type ToolKind

type ToolKind string

ToolKind 是工具调用的语义分类,供 tool_use/tool_result 事件消费方 区分读写文件、shell、搜索、网页抓取、MCP、subagent、todo 等类别。

const (
	ToolKindFileRead  ToolKind = "file_read"  // 读文件
	ToolKindFileWrite ToolKind = "file_write" // 写/改文件
	ToolKindShell     ToolKind = "shell"      // 执行 shell
	ToolKindSearch    ToolKind = "search"     // 搜索(代码/文件/网页)
	ToolKindWebFetch  ToolKind = "webfetch"   // 抓取网页
	ToolKindMCP       ToolKind = "mcp"        // 调用 MCP 工具
	ToolKindSubagent  ToolKind = "subagent"   // 发起 subagent
	ToolKindTodo      ToolKind = "todo"       // todo 读写
	ToolKindOther     ToolKind = "other"
)

ToolKind 取值常量;行尾注释为各分类的覆盖范围。

func ClassifyTool

func ClassifyTool(name string) ToolKind

ClassifyTool 把工具名归类为 ToolKind。未知名称再按 MCP 命名试探 (opencode 把 MCP 工具注册为 server_tool 形式,无统一前缀,属尽力而为), 都不命中返回 ToolKindOther。

type ToolState

type ToolState struct {
	Status string         `json:"status"` // pending | running | completed | error
	Input  map[string]any `json:"input,omitempty"`
	Output string         `json:"output,omitempty"`
	Error  string         `json:"error,omitempty"`
}

ToolState 是 tool part 的执行状态。

type UpdateSessionReq

type UpdateSessionReq struct {
	Title    string         `json:"title,omitempty"`
	Metadata map[string]any `json:"metadata,omitempty"`
	Archived int64          `json:"-"` // 毫秒时间戳;>0 时上送 time.archived
}

UpdateSessionReq 是 PATCH /session/{id} 的请求体;零值字段不上送。

Directories

Path Synopsis
examples
auto-reply command
Command auto-reply 演示在事件流里自动处理 PermissionAsked / QuestionAsked。
Command auto-reply 演示在事件流里自动处理 PermissionAsked / QuestionAsked。
basic command
Command basic 演示最小集成路径:Health → ListModels → CreateSession → Run → 收 HighEvent → 清理。
Command basic 演示最小集成路径:Health → ListModels → CreateSession → Run → 收 HighEvent → 清理。
concurrent command
Command concurrent 演示用一条 GlobalEventStream 长连服务多个 session, 适用 HTTP 网关 / 机器人适配层等"一进多出"场景。
Command concurrent 演示用一条 GlobalEventStream 长连服务多个 session, 适用 HTTP 网关 / 机器人适配层等"一进多出"场景。
observability command
Command observability 演示 v0.2 引入的可观测性与可靠性能力:
Command observability 演示 v0.2 引入的可观测性与可靠性能力:
session-crud command
Command session-crud 串起 session 管理面:Create → List → Get → ListMessages → Delete,演示一次完整生命周期。
Command session-crud 串起 session 管理面:Create → List → Get → ListMessages → Delete,演示一次完整生命周期。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL