跳转到内容
导航 打开 esc关闭

Host API

当你需要准确了解 connectHost 的方法、限制和错误码时,使用本页。关于文件夹结构、清单和安装,请从构建扩展开始。

import { connectHost, HostRequestError } from "@openchamber/sdk";
const host = connectHost();

没有 window 时,connectHost 抛出 HOST_UNAVAILABLE。在 OpenChamber 之外,它返回一个客户端,其每次调用都以 HOST_UNAVAILABLE 拒绝。卸载页面时调用 dispose();仍在进行中的调用会以同一错误码拒绝。

OpenChamber 推送给你的内容

onReady 在第一份快照到达时触发,之后每当 OpenChamber 刷新快照时再次触发。

它不是只执行一次的挂载回调。UI 挂载和订阅注册只做一次,之后更新主题和上下文,不要替换输入框或草稿。参见 UI kit 中的防重复挂载示例。onConnection 等监听器会立即提供当前值,并可能在新快照中再次提供相同值。发起新请求前比较相关字段,并忽略已被新请求替代的旧响应。

字段含义
theme.modelightdark
theme.tokens应用的颜色(表面、文本、交互状态、primary、success/warning/error/info),以及 fontmonoradius。在挂载 UI 之前把它传给 applyHostReady
locale应用的语言标签
directory当前项目目录,或 null
session{ id, title, busy }null。没有标题时回退为会话 id。busy 是实时状态。会话有 modelagent(OpenCode 的 agent)时也会出现。
surface侧栏为 panel,附加窗口为 dialog,全屏为 page
item此界面因何而打开,或 null:用户点击的已附加条目(与你传给 attach 的字段相同,包括 data)、来自你声明的某个操作的消息(kind: "message")或会话(kind: "session")。见操作
connection你的集成的 { connected, account }
settings你在 integration.settings 中声明的字段的值

访问令牌永远不会出现在这里,也不会出现在 request 的结果中。

onDirectoryonSessiononSessionLifecycleonConnectiononSettingsonItem 在你较晚订阅时会重放最新值,之后随变化持续触发。

theme.tokens 包含 primaryTextsuccessTextwarningTexterrorTextinfoText。这些必填字段提供宿主计算的文字颜色,用于中性背景和 UI kit 的浅色控件,而非实心彩色填充。applyHostReady 会在每次快照中应用这些值。CSS 变量见 UI kit

方法

调用作用
toast({ kind, message })在应用中显示提示消息。kindinfosuccesserror
openUrl(url)在用户的浏览器中打开 URL
openSurface(surfaceId)把应用切换到该界面
writeClipboard(text)复制文本。1 到 32000 个字符。
compose({ text, mode? })把文本放进聊天框但不发送。modeappend(默认)或 replace。去除首尾空白后 1 到 16000 个字符。
attach({ ... })在聊天框上放一个标签,和 GitHub、Linear 条目落在同一位置。同一时间只能有一个标签。
startSession({ ... })创建一个附加了该条目的会话。返回 { sessionId, sent }。需要 sessions 能力。
prompt({ text, send? })写入当前会话,或在当前会话中发送。返回 { sent }。发送需要 prompt 能力。
sessionLink({ ... })把条目附加到当前会话,而不创建新会话
close()关闭附加窗口。在侧栏中无效果。
oauthStart()打开提供方的授权页面;对于 host: { provider: "linear" } 集成,打开 Linear 的授权页面
oauthDisconnect()忘记已存储的令牌,或断开 Linear 连接
request({ method, path, query?, body? })调用你的集成的 apiOrigin,并附上用户的令牌
serviceRequest({ method, path, query?, body? })调用你的扩展的本地服务(见 GUEST_SERVICES.md
serviceStatus()stoppedstartingreadyfailed
readFile(path)读取文本文件。返回 { content }
writeFile(path, content)原子地写入文本文件,并创建父文件夹。返回 { written: true }
listDir(path)列出文件夹。返回 { entries: [{ name, kind }] }kindfiledirectoryother
stat(path){ kind, size, mtime }kindfiledirectoryothermissing
generate({ prompt, system?, maxOutputTokens? })来自用户 Small Model 的一次性文本。返回 { text }。需要 model 能力。
onResolve(handler)注册你的斜杠命令处理函数。它收到 { command, args },返回一个要附加的条目或 null
setBadge(count)在你的侧栏图标上显示一个数字(0 到 999),或传 null 清除

三者接受相同的条目字段:

await host.attach({
providerId: "acme-hello",
id: "TICKET-1",
title: "Login is broken",
url: "https://example.com/TICKET-1",
});
await host.attach({
providerId: "acme-hello",
id: "!12",
title: "Fix login",
url: "https://example.com/merge_requests/12",
kind: "pull",
author: "ada",
branches: { head: "feature", base: "main" },
text: "Optional notes for the model",
});
await host.startSession({
providerId: "acme-hello",
id: "!12",
title: "Fix login",
url: "https://example.com/merge_requests/12",
kind: "pull",
worktree: true,
text: "Optional first message",
});

providerId 是你的面板 id;无论如何 OpenChamber 都会用你的 id 覆盖它。id 是你自己为条目定义的标识符。kindissue(默认)或 pulltext 是给模型的可选上下文,去除首尾空白后 1 到 16000 个字符。data 是你自定义的可选 JSON(状态、评论,任何内容),序列化后最多 16000 个字符。OpenChamber 会把它和标签一起保存,并在用户点击标签时通过 ctx.item 原样交还;它永远不会到达模型。

startSession 接受 projectId,无需切换当前项目。省略 worktree 表示目标目录,true 创建自动命名的工作树,{ kind: "existing", directory } 选择已知工作树,{ kind: "new", name?, baseBranch? } 指定新工作树名称和基准分支。名称同时用于分支。navigation 默认为 "preserve""open" 打开新聊天。首条消息的模型、代理和变体在创建开始时确定。

结果包含 sessionIddirectorysentlinked 及可选 worktreesentsentno-modelskippedfailedlinked: false 表示条目保存失败。准备或会话创建失败后若保留了工作树,返回 sessionId: nullfailure: "bootstrap-failed" | "session-create-failed"。重试前请检查结果。最多等待180秒;超时不代表已回滚创建。

sessionLink 把条目附加到当前打开的会话。没有项目或没有会话时是 NO_SESSION 错误。

客户端在发送任何内容之前应用的限制:

字段最大字符数
id128
title200
url2000
text16000
data(序列化后)16000
author80
每个分支名200

prompt 与会话生命周期

await host.prompt({ text: "Fix the login" });
await host.prompt({ text: "Fix the login", send: true });
host.onSessionLifecycle((event) => {
document.body.dataset.phase = event.phase;
});

不带 send 时,prompt 会替换聊天框中的文本。带 send: true 时,它会用用户已选择的模型和 agent 发送消息。扩展永远不会自己选择它们。没有打开的会话时是 NO_SESSION。会话忙碌时发送是 SESSION_BUSY;忙碌时写入聊天框仍然可以。结果是 { sent },取值与 startSession 相同。

onSessionLifecycle 告诉你会话正在做什么。phase 在模型工作时为 started,在会话转为空闲时为 completed,出现意外状态时为 failure。较晚注册的监听器会立即收到当前阶段。

项目、实时会话和存储

listProjects()listWorktrees(projectId)listSessions(projectId) 使用已有 sessions 权限读取共享状态,不会每次遍历 Git,也不暴露对话内容。项目包含 ID、名称和目录;工作树包含分支和可用状态。会话包含时间戳、父会话、工作树,以及仅属于此扩展的附加条目。包括已知的归档会话。

await onProjects(listener)await onWorktrees(projectId, listener)await onSessions(projectId, listener) 返回取消订阅函数。请处理注册失败。先发送初始快照,再发送更新。每个 iframe 最多32个订阅;dispose()、关闭、暂停、移除和切换服务器都会清理订阅。stateloadingreadyerror,会话另有按目录的 coverage。失败保留已有数据,只有 ready 表示完整结果,包括真正的空列表。

activityunknownidlerunningretryingwaiting-permissionwaiting-questionoutcome 为观测到的 completedfailednull,仅在内存中保留最多2,000个会话,不从历史推断。错误后的 idle 保留失败结果直到下次运行。这不代表任务完成。openSession(sessionId) 显式打开聊天。

host.storage.get(key)set(key, value)delete(key)keys() 无需额外权限即可在连接的服务器保存扩展自己的 JSON。缺少的键返回 undefined,保存的 null 仍为 null。键长1到128字符,每个值最多64 KiB UTF-8,整个空间最多2 MiB及2,000个键。项目专属数据可将项目 ID 加入键名。写入串行且原子化,失败保留已有数据。卸载扩展会删除存储。

request

const user = await host.request({ method: "GET", path: "/api/v2/user" });

path/ 开头,不含协议和主机名;OpenChamber 会把它拼接到清单中的 apiOrigin 之后,并添加 Authorization 头。令牌集成会原样发送粘贴的令牌;当清单设置了 token.scheme: "bearer" 时,发送为 Bearer <token>;设置为 "basic" 时,发送为 Basic base64(username:token)。OAuth 和 Linear 集成始终发送 Bearer。结果是 { status, body }。body 是文本,JSON 需要你自己解析。

Linear 集成还可以 GET /api/linear/issues/get,OpenChamber 会用自己的 Linear 路由来响应。

20 秒内没有响应是 HOST_TIMEOUT

try {
await host.request({ method: "GET", path: "/api/v2/user" });
} catch (error) {
if (error instanceof HostRequestError && error.code === "DISCONNECTED") {
await host.oauthStart();
}
}

serviceRequest

当清单声明了 contributes.service 时,OpenChamber 会启动该进程,你的页面通过同样类型的调用与它交互。页面永远不会自己打开 socket。

const status = await host.serviceStatus();
const result = await host.serviceRequest({ method: "GET", path: "/containers" });

path 遵循与 request 相同的规则。声明本地服务会把 service 加入用户在安装时批准的能力;在批准之前,每次 serviceRequest 都是 NO_SERVICE。完整约定见包内文件 GUEST_SERVICES.md

文件

相对路径位于打开的项目中,需要 files 能力。以 /~/ 开头的路径必须匹配已声明的 contributes.filesystem 模式,需要 filesystem 能力。规则见构建扩展

const { content } = await host.readFile("package.json");
await host.writeFile("notes/today.md", "# Today\n");
const { entries } = await host.listDir("src");
const info = await host.stat("~/.config/opencode/opencode.json");

限制:路径最长 1024 个字符,内容最多 2,000,000 个字符,列表最多 2000 项。内容超出限制为 FILE_TOO_LARGE

generate

const { text } = await host.generate({ prompt: notes, system: "Summarize in one sentence." });

prompt 在修剪后为 1 到 64,000 个字符,system 最多 8,000 个字符,maxOutputTokens 为 1 到 4,000。内容不会进入会话,也不保留历史记录;扩展永远不能选择模型。调用最多等待 90 秒。没有可用的 Small Model 是 NO_MODEL;模型出错是 MODEL_FAILED。见生成文本

错误码

失败的调用会抛出 HostRequestErrorcode 是下列之一;message 提供更多说明。

错误码何时出现
HOST_UNAVAILABLE没有 window、不在 OpenChamber 内,或已执行 dispose()
HOST_TIMEOUT20 秒内没有响应
HOST_REJECTEDOpenChamber 拒绝了请求,或返回了本 SDK 不认识的错误码
DISCONNECTED你的集成没有令牌或 Linear 连接
BAD_PATHpath 格式错误,或试图离开允许的源
NO_INTEGRATION清单中没有 integration
NO_SESSION没有打开的会话时调用了 promptsessionLink
SESSION_BUSY会话忙碌时调用了 prompt({ send: true })
DISABLED用户在 设置 → 扩展 中暂停了该扩展
NO_SERVICE未声明本地服务、未批准或未运行
NOT_GRANTED用户尚未批准此调用所需的能力
SERVICE_FAILED本地服务崩溃或从未就绪
NO_DIRECTORY在没有打开项目的情况下使用了相对文件路径
NOT_FOUND对不存在的文件调用 readFile
FILE_TOO_LARGE读取或写入时文件内容超出限制
DENIED操作系统拒绝了该文件操作
NO_MODEL没有可用的 Small Model 时调用 generate
MODEL_FAILEDSmall Model 返回了错误

相关页面

  • 构建扩展:文件夹、清单、安装和能力
  • UI 套件:按钮、输入框、列表和其他构件