集成AgentOpenCode

用 Litefuse 追踪 OpenCode

OpenCode 是一个开源的终端编码 Agent,采用客户端/服务端架构、运行在 Bun 上。本集成是一个 OpenCode 原生插件,安装在 ~/.config/opencode/plugin/litefuse.ts —— 单个 TypeScript 文件,零 npm 依赖。把它放进插件目录即自动加载,无需改 opencode.jsonc

插件运行在 OpenCode 服务端进程内,订阅它的事件总线与若干专用 hook(chat.messagemessage.part.updatedstep-start/step-finishtool.execute.before/aftersession.idle),为每个用户回合生成一条 Litefuse trace,使用真实的逐事件时间戳。Span 以裸 OTLP/HTTP JSON 形式、用内置 fetch 直接发往 Litefuse 的 OTLP 端点 —— 没有 SDK、没有 node_modules、不需要构建步骤(OpenCode 直接加载 TypeScript)。

给 AI —— 自动安装

如果你正在和 OpenCode 对话,把下面这段 prompt 粘贴过去,Agent 会端到端完成整个安装:

Read https://litefuse.ai/SKILL.md and follow the instructions to install and configure Litefuse for OpenCode.

skill 会向你索取 Litefuse 的 API Key(如果还没有账号,会引导你注册),然后在本地完成全部配置。如果你想手工一步步配置,请继续往下看。

会捕获哪些数据

数据捕获方式备注
用户 prompttrace input文本;图片 block 数量记入 metadata
每次 LLM API 调用generation observationplan (n tools) #N / response / think #N,按模型这一步做了什么命名
thinking / text / toolCall blockgeneration output保留块结构,含 reasoning 文本
首 token 时间generation 的 completion_start_time来自第一个流式 part —— 驱动 Litefuse 的 TTFT 指标
采样参数generation 的 model_parameterstemperature / topP / topK / maxOutputTokens,模型设置了才采集
token 用量(inputoutputcache_read_input_tokenscache_creation_input_tokensgeneration 的 usage_detailsAnthropic 风格 key,来自 OpenCode 每步的 step-finish
模型名 + OpenCode 自带成本generation 的 model + cost_detailsOpenCode 给出权威成本,覆盖服务端价格表
工具执行tool observationtool: bash (grep) #Ntool: read (index.ts) #N —— 名字带关键信息,完整参数在 input
子 agenttask 工具)子树tool (1 subagent) #Nsubagent 容器 → 子 agent 自己的 plan/tool/response 步骤;子成本汇入父 trace
工具错误tool observation,level=ERROR来自 tool part 的 error 终态,带 status_message 预览
LLM 错误 / 中止generation level=ERROR / WARNING来自 assistant 消息的 error 字段
上下文压缩context compaction event解释下一次调用 input token 骤降
没有最终回答收尾的回合根 span level=WARNING在工具循环中被杀或中断
会话分组trace 的 session_idOpenCode 会话 id(ses_…);续接会话 Turn 编号接着数
用户身份trace 的 user_id$LITEFUSE_USER_ID,回退到系统用户名
标签trace 的 tagsopencode + model:<name>

Trace 结构

一个委派了子 agent 的典型回合产生的 trace 形如(真实示例):

opencode — Turn 1                        (AGENT root —— 真实回合时长)
├── plan (1 tool) #1                     (generation,usage_details + 成本,真实延迟)
├── tool (1 subagent) #2                 (tool —— 父进程视角的委派)
│   └── subagent                         (AGENT 容器 —— 子会话)
│       ├── plan (1 tool) #1             (容器内局部编号,从 #1 重计)
│       ├── tool: read (note.txt) #2
│       └── subagent response            (generation —— 子 agent 的最终回答)
└── response                             (generation —— 最终回答,结束回合)

设计说明:

  • Generation 按”模型这一步做了什么”命名,而不是用哪个模型(plan (2 tools)responsethink)—— 模型名是 generation 的 model 属性,换模型不会破坏看板。OpenCode 把一次 LLM 调用表示成一对 step-start / step-finish part,插件据此切分 generation,token 与成本取自 step-finish
  • Generation 延迟不含工具时间。 OpenCode 的 step-finish 在该步工具执行之后才发,因此插件把 generation 的结束时间取在最后一个流式内容片到达的时刻(≈ LLM 真正返回的时刻),把工具耗时排除在 LLM 延迟之外 —— token/成本仍取自权威的 step-finish
  • 每个 agent 容器一个步骤计数器#N 由 generation 和 tool 按时间顺序共用。tool 的 metadata agent_plan_step 指向发起它的 plan 的 agent_step_index —— join 条件是 tool.agent_plan_step == generation.agent_step_index。每个子 agent 容器从 #1 重新计数,层级由树结构表达。
  • 子 agent 子树。 OpenCode 是单服务端进程,一个插件实例能看到所有会话的事件 —— 包括 task 工具派生的子会话。因此子树无需跨进程的 traceparent 协议:插件用子会话的 parentID 把它绑到父进程仍在执行的 task 工具 span 上,形成 tool (1 subagent)subagent 容器 → 子步骤的三层结构。工具 span 包裹容器是有意设计:tool span 时长 − 容器时长 = 委派的真实开销(子会话启动、结果回收)。
  • 每个 span 恰好发送一次、在它结束时 —— OTel 的 span 不可变;没有临时发送、没有 upsert。trace header(name / session / user / input / tags)随每个 span 携带,所以第一个 observation(通常是几秒内完成的 plan #1)一完成,trace 就出现在 Litefuse 里 —— 长回合执行中即可见。
  • 真实 wall-clock 时间戳,来自每个事件触发的时刻 —— 时间线反映真实的 LLM 延迟、工具耗时与间隔。
  • 打平的 agent_* metadata:所有 OpenCode 专属字段都是带统一前缀的顶层 metadata key(agent_step_indexagent_plan_stepagent_duration_ms……)—— 同一个看板过滤条件对所有 Litefuse agent 集成通用。稀疏存储:没有值的字段完全不出现,不用 null 占位。
  • Fail-open:任何意外错误写入 ~/.config/opencode/litefuse.log 后静默吞掉 —— 插件永不阻塞 OpenCode。不可达的目标直接跳过。

快速开始

前置条件

  • 已安装 OpenCode —— 用 opencode --version 检查。
  • curlPATH 上(macOS / Linux 默认都有)。插件用它同步投递每个回合的根 span,详见工作原理
  • https://litefuse.cloud 创建一个 Litefuse 项目,拿到 public + secret key。

没有其他依赖:插件是只用 Node 内置模块的单文件,OpenCode 直接加载 TypeScript。

下载插件

mkdir -p ~/.config/opencode/plugin
curl -fsSL https://litefuse.ai/integrations/opencode/litefuse.ts \
  -o ~/.config/opencode/plugin/litefuse.ts

源码也在同一 URL 上 —— 部署前可以先读一遍。~/.config/opencode/plugin/ 下的文件会被 OpenCode 自动加载,无需改 opencode.jsonc

配置凭据

创建 ~/.config/opencode/litefuse-targets.json(不需要改 shell 配置文件):

cat > ~/.config/opencode/litefuse-targets.json <<'EOF'
[
  {
    "publicKey": "pk-lf-xxx",
    "secretKey": "sk-lf-xxx",
    "baseUrl": "https://litefuse.cloud",
    "environment": "production"
  }
]
EOF
chmod 600 ~/.config/opencode/litefuse-targets.json

把占位值替换成真实值。也可以改用环境变量 LITEFUSE_PUBLIC_KEY / LITEFUSE_SECRET_KEY / LITEFUSE_BASE_URL(同名 LANGFUSE_* 作为 fallback 也接受),在启动 OpenCode 的 shell 里 export 即可。targets 文件支持多个目标 —— 同一份 trace 会写入每一个 Litefuse 实例,各目标可配置独立的 environment

验证

OPENCODE_LITEFUSE_DEBUG=true opencode run "Reply with exactly: ok"
tail -3 ~/.config/opencode/litefuse.log
# 期望: "plugin loaded, 1 target(s): https://litefuse.cloud (production)"
#     + "turn complete session=... turn=1 api=1 tools=0"

打开 https://litefuse.cloud —— 最新一条 trace 名为 opencode — Turn 1,结构如上所示。

OpenCode 在启动时从插件目录加载插件:装好插件或改完凭据后,新启动的 opencode run 或新开的会话即自动生效;已经开着的 TUI 里新建一个会话即可。

如果没有配置任何 key,插件会保持空闲(零开销),不发送任何数据。

环境变量

LITEFUSE_* 变量优先;同名 LANGFUSE_* 变量作为生态兼容的 fallback 也接受。

变量必填说明
LITEFUSE_PUBLIC_KEY是*Litefuse 项目 public key(pk-lf-...)。
LITEFUSE_SECRET_KEY是*Litefuse 项目 secret key(sk-lf-...)。
LITEFUSE_BASE_URL默认 https://litefuse.cloud。别名 LITEFUSE_HOST
LITEFUSE_TRACING_ENVIRONMENTenv 配置目标的 trace environment。默认 production;做实验用 development,免得污染生产看板。
LITEFUSE_USER_ID覆盖 trace 的 user_id。回退到系统用户名。
LITEFUSE_EXTRA_TARGETS额外目标的 JSON 数组(与 targets 文件同构)。
OPENCODE_LITEFUSE_DEBUG设为 "true" 启用 ~/.config/opencode/litefuse.log 的详细日志。
OPENCODE_LITEFUSE_MAX_CHARSspan 输入/输出的截断阈值(字符数)。默认 1000000(约 1MB 文本)。

* 仅在不使用 ~/.config/opencode/litefuse-targets.json 时必填。targets 文件里的凭据无需任何环境变量即可工作。

Trace metadata 参考

所有 OpenCode 专属字段都是agent_ 前缀的打平顶层 metadata key —— 所有 Litefuse agent 集成共用同一套 key,同样的过滤器和看板处处可用。Litefuse 标准字段(sessionIduserIdtags)通过 OTel 属性保持在 trace 级。稀疏存储:没有值的字段完全不出现,绝不用 null

Trace 级:

  • agent_turn_numberagent_session_idagent_cwdagent_modelagent_provider
  • agent_api_callsagent_tool_callsagent_stepsagent_duration_ms —— 回合统计
  • agent_image_blocksagent_prompt_truncatedagent_prompt_orig_len —— 适用时出现

Generation observation:

  • agent_step_index —— 名字里的 #N
  • agent_provideragent_stop_reason
  • agent_api_duration_msagent_time_to_first_token_ms
  • agent_tool_call_countagent_reasoning_tokensagent_thinking_chars
  • agent_input_truncatedagent_output_truncatedagent_output_orig_len —— 仅截断时出现

Tool observation:

  • agent_tool_nameagent_tool_call_id
  • agent_step_index —— 工具自己的 #N
  • agent_plan_step —— 发起本工具的 plan generation 的 agent_step_index
  • agent_duration_msagent_is_error
  • agent_details —— OpenCode 的结构化工具结果摘要(退出码、标题等),≤ 2 KB 时以对象内嵌;过大则整体省略并记 agent_details_omitted_len

Subagent 容器:

  • agent_subagent: true,外加与 trace 级相同的单次运行统计(agent_api_callsagent_tool_callsagent_stepsagent_duration_ms

工作原理

插件订阅 OpenCode 的事件总线和几个专用 hook:

事件 / Hook用途
chat.message(user)开启回合:捕获 prompt;从会话历史恢复 Turn 编号
chat.params采样参数 → generation 的 model_parameters
experimental.chat.messages.transform发往 provider 的完整 messages → generation 的 input(尽力而为)
message.part.updatedstep-startgeneration 开始
message.part.updatedtext / reasoning / toolgeneration 输出块、TTFT、工具调用计数
message.part.updatedstep-finish发出 generation span:按内容命名,带 usage + 成本
tool.execute.before / tool.execute.aftertool span;task 工具登记为子树容器的父
message.part.updated(tool error 终态)工具失败 → level=ERROR(权威来源)
session.compacted上下文压缩 event observation
message.updated(assistant)模型 / provider / 错误信息
session.idle发出 root span:trace output、回合统计;flush

回合内每个完成的 span 立即以 OTLP/HTTP JSON、fire-and-forget 加超时 POST 到 <baseUrl>/api/public/otel/v1/traces(Basic auth)。每个 span 只发送一次;执行中的步骤在完成时变为可见。

根 span 同步投递。 OpenCode 的 headless runsession.idle 后会立刻退出,不等待插件的事件处理器完成 —— 此时根 span(回合最后发出的 span)的异步发送会与进程拆毁竞速而丢失。为此插件用一次同步 curl(阻塞 JS 线程直到投递完成)发出回合结束的根 span 批次,保证它一定落库;回合内其他 span 仍走异步、不阻塞。这就是前置条件里需要 curl 的原因。

对于子 agent,OpenCode 的 task 工具会派生一个子会话。子会话的事件流经同一个插件实例;插件用子会话的 parentID 把它绑到父进程仍在执行的 task 工具 span 下,生成完整子树。子 agent 的 generation 各自带 usage,成本自动汇入父 trace 总成本。一个已知暂态:live 视图里子 agent 的步骤可能先于其容器出现(容器在子会话结束时才发送)—— 回合结束后树完整且嵌套正确。

插件是 fail-open 的:任何意外错误只写 ~/.config/opencode/litefuse.log 并静默返回,绝不阻塞或拖慢 OpenCode。

故障排查

Litefuse 中没有出现 trace。 查看插件日志:

tail -20 ~/.config/opencode/litefuse.log

日志为空说明插件没加载 —— 确认文件在 ~/.config/opencode/plugin/litefuse.ts。文件存在但没有 plugin loaded 行?插件没找到凭据:检查 ~/.config/opencode/litefuse-targets.jsonLITEFUSE_* 环境变量。有 sendSync failed:send failed: 说明 key 或网络问题:核对 key 与 LITEFUSE_BASE_URL

根 span 缺失(trace 有子节点但没有 AGENT 根)。 几乎都是 curl 不在 PATH 上 —— 插件靠它同步投递根 span。日志里会有 sendSync failed: ... exit=...。装上 curl 即可。

某个工具是失败的,但没标成 ERROR。 OpenCode 的 bash 工具把命令的非零退出当作成功完成(stderr 进 output、退出码进 metadata),不算工具级错误 —— 这符合 OpenCode 的语义。真正的工具错误(如 read 一个不存在的文件)会正确地标为 level=ERROR

totalCost 是 0。 用的是免费模型(成本本就为 0),或 Litefuse 项目里没有该模型的价格条目。需要时在 Litefuse UI 的 Settings → Models 里添加 —— 不过 OpenCode 会自带成本数据,插件以 cost_details 转发,付费模型的成本会直接显示。

子 agent 没有再嵌套(孙 agent)。 OpenCode 的子会话目前不带 task 工具,所以子 agent 无法再委派 —— 插件的递归子树支持已就绪,等上游放开即可生效。

启用调试日志:

OPENCODE_LITEFUSE_DEBUG=true opencode
# 每次发送 / 跳过 / 错误都会写入 ~/.config/opencode/litefuse.log

本地 + 云端双写:litefuse-targets.json 里加第二个条目(例如本地自托管实例配 "environment": "test")。不可达的目标会被静默跳过,永不阻塞 OpenCode。

资源

这个页面对你有帮助吗?