Skip to content

Providers and Agents zh

Hermes Agent edited this page Oct 1, 2026 · 7 revisions

Provider 层与 Agent 接入

English | 中文 | 日本語 | 한국어 | Español | Português | Русский

四种 wire protocol(apiStyle)

lib/llm-client.js 是协议层。四条流共用一个 openSSEStream() 骨架(fetch / 错误分类 / 预算协商 / abort / SSE 切帧 / 收尾),每协议只留 buildRequest + 纯事件解析;SSE data: 载荷抽取单份(sseDataPayload,多行 data 合法拼接):

apiStyle 端点 备注
chat /v1/chat/completions 另解析 DeepSeek 风格 reasoning_content
responses /v1/responses 原生 input_* 多模态拼写
anthropic /v1/messages 显式 cache_control 两断点(系统提示 + 最后消息)——Anthropic 无隐式前缀缓存
runs Hermes /v1/runs agent 协议:审批/澄清/工具/思考事件

thinking: 'inline' | 'omit'(默认 omit)控制推理文本是否包进一个 <thinking> 块内联在 delta 流里;只有主聊天与追问卡用 inline。runs 的 reasoning.available 是答案重放不是思考,echo guard 必须丢弃(ADR-0004)。

回合请求阶梯

主聊天的每-provider 请求形状四度重建(初始 / 溢出重建 / 续写 / 时间戳改写),收拢为 createTurnRequest(prepare / rebuildFrom / continueWith / rewriteWith)——chat-handler 留「何时」,阶梯管「如何」。追问卡故意不并入(独立会话语义,ADR-0007)。

输出预算与截断

  • 默认 max_tokens 32768;服务器拒收超帽(400)时 renegotiateOutputCap 从错误文本提取真帽重试一次。
  • finish_reason === 'length' → 一次静默续写(防重复指令,silent 第二趟);仍截断 → DONE 带 outputTruncated,面板 toast + 「继续生成」按钮。这里没有暴露 max_tokens 手填框——「需要逐模型知识的旋钮是设计 bug」(用户原话)。

四家 Agent

Agent 通道 会话
Hermes /v1/runs 服务端会话(storage 持 sessionId);当前轮部件用规范 text/image_url,conversation_history 一律字符串(严格层 422 实锤,2026-09-24)
OpenCode opencode serve HTTP 服务端会话;随机端口 → 推荐固定 --port
OpenSquilla 本地网关 ws://…/ws 每对话一个网关会话;超 6 万字符附件变 page-context.md 文档上传;origin 白名单见安全模型页
Agent Bridge @xiaohuzai/agent-bridge 本地守护 codex/claude/pi/gemini 适配统一 HTTP 协议;订阅登录即可当模型源(gemini 为 Google 登录)

公共层 lib/agent-turn.js:agent 回合只发用户当前轮 + 尾部页上下文连续段(transcript 在服务端,不重发历史);图片走 pickTurnImages(≤8 张 / ≤3MiB URL 预算,发送侧与附加侧同一规则族);切换到 agent provider 时的「带上当前对话继续」= 一次性 backfill(纯文本转录,尾部 200K 封顶)。

跨入口接力:agent 侧会话命名与 ID(2026-10-01)

对 agent provider,transcript 本来就在 agent 侧(agent 回合只发当前轮)——「让 agent 自己的 UI 接手会话」缺的只是可发现性。两件事:

  • 自动命名:hermes 回合成功后一次性 PATCH /api/sessions/{id}(上游原版 API,服务端净化标题、精确重名拒收),标题 = browsa: + 当前轮首条用户文字(48 字封顶)。盖戳语义(hermesSessionTitled_<provider>,与会话 ID 同生命周期):成功与 4xx 都盖戳(重名不许变成逐轮重试),仅传输失败(status 0)留给下个成功回合;PATCH 自身 10s 超时、await 与 2.5s 上限竞速,绝不拖慢 DONE。四家 bridge agent 的接续能力(2026-10-01 全量查证,源码级):
agent picker 可见 命名通道 接续方式
codex ✓(picker 条件 has_user_event=1 AND title<>'',真实回合即满足;DB 全局无 cwd 隔离) 桥 POST /threads/{id}/title → app-server thread/name/set(已接) 按名或按 ID codex resume
claude ✓(桥侧 transcriptFix 落盘把 entrypoint: sdk-* 翻成 cli,#56;Mac 实机确认) 无 client 改名通道(claude-agent-acp 自带 AI 标题) claude --resume(picker 或按 ID)
pi ✓(ACP 会话落 pi 自己的 cwd 范围存储,picker 同店可读;默认 Current Folder 范围,tab 切 All、ctrl+n 按名过滤) pi 原生 set_session_name RPC(session_info 名条写入 jsonl)——pi-acp 只在回合内 /name 命令暴露,桥未接 pi --resume(picker)/ --session-id <ID>
gemini ✓(列表只剔除 kind:"subagent",bridge 会话 kind:"main" 在列;~/.gemini/tmp/<cwd>/chats/ 同店) 无命名通道(标题取首条用户消息) gemini --resume(index/ID,cwd 范围)

opencode/squilla 待逐家验证。追问卡的专用会话故意不命名。

  • 会话 ID 可见:storage.getAgentSessionInfo 单点持有四种 agent 的会话键形状(bridge 按端点,activeModel || baseUrl);会话抽屉列表上方渲染「Agent 会话」行(短 ID + 复制),LLM provider / 无会话时隐藏。

审批 / 澄清中继

agent 的危险操作审批与反问澄清经 lib/handlers/approval-relay.js 单点中继(主聊天 tabId 键 / 追问卡 subId 键双路)。pending 条目的形状就是分派接口。UI 侧 turn-chrome.js 是主聊天与追问卡共享的回合外壳(思考中指示、工具进度、审批卡、用量 chip)。

Provider 选择 UI 的既定规则

  • 下拉只列已配置的 provider,可达者优先(稳定排序);零配置时显示禁用占位项。
  • 存储的 activeProvider 不在已配置集 → 自动改选第一个配置项并持久化(修状态不是改偏好);首次 ping 可达也自动切换(只一次)。
  • 多模型 provider:模型 ID 逗号分隔;下拉逐模型展开;resolveChatModel 只认仍属该 provider 的 activeModel。
  • LLM/Agent 两组都是 tab 式卡片(agent 组默认折叠;tab 顺序 [bridge, opencode, hermes] 被测试钉住)。

CAPABILITY_HINTS 经济学(ADR-0010,勿再提压缩)

每 CHAT 回合系统提示 = 用户 systemPrompt + 回复语言行 + CAPABILITY_HINTS + CHOICE_REQUEST_HINT;必须保持字节稳定前缀(逐轮关键词门控会破坏 KV prompt-cache 从 0 开始的前缀)。每个残留条目都是用真渲染 bug 换来的——没有查过 AGENTS.md 历史不许「精简」它的 why 句。渲染器漏格式的修法是更好的提示,不是运行时检测。


源头:AGENTS.md「Provider API styles」、chat/subchat-handler、provider-resolver、agent 各节;ADR-0004 / 0007 / 0010。同步于 2026-10-01(含跨入口接力)。

Clone this wiki locally