# 业务平台接入指南 把 AgentServer 的对话能力(Claude / Codex、流式输出、审批与提问、文件上传与产出)接入自己的平台(Vue 3 + TS)。可以直接对照完整的示例项目 [`examples/platform-demo`](../examples/platform-demo):它由一个 Vue 前端和一个约 50 行的 Node 后端组成,同时演示了单个助手的对话和机器人工作组(§8)。 ## 0. 工作方式 ``` 浏览器(平台前端) │ ① POST /api/agent-token(平台自己的接口,校验平台登录态) ▼ 平台后端 ── ② POST /v1/browser-tokens(携带 Agent Key)──► AgentServer │ 返回 { token: "bt_…", expiresAt }(默认 1 小时) ▼ 浏览器 ── ③ 用 bt_ 令牌直接调用 AgentServer 的 /v1(跨域,SSE 流式)──► AgentServer ``` - **Agent Key 只放在平台后端**,不要写进前端代码或前端环境变量。 - 浏览器令牌的权限与 Agent Key 相同:只能使用该 Key 可用的 Profile,只能看到该 Key 创建的会话。停用或删除 Key 后,令牌立即失效。 - 一个平台一个 Agent Key,AgentServer 不区分平台内的具体用户。如果需要区分,在平台侧记录会话 ID 与用户的对应关系。 ## 1. 在 AgentServer 后台准备 1. **API Keys › Agent Keys › 签发 Agent Key**:勾选这个平台可以使用的 Profile,并设好默认 Profile。Key 只显示一次,存到平台后端的密钥配置里。 2. **设置 › 安全 › CORS 来源**:加入平台前端的地址,例如 `https://tools.example.com`(只写协议、域名和端口,不带路径)。 3. **Agent Keys** 页面底部的"业务平台接入"卡片里有当前 SDK 版本的安装命令,可以直接复制。 ## 2. 平台后端:签发浏览器令牌 任何语言都可以实现,只需要转发一次请求。Node 示例: ```ts // POST /api/agent-token —— 先校验平台自己的登录态 app.post('/api/agent-token', requireLogin, async (req, res) => { const r = await fetch(`${process.env.AGENT_SERVER_URL}/v1/browser-tokens`, { method: 'POST', headers: { authorization: `Bearer ${process.env.AGENT_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ ttlSeconds: 3600 }), // 60 ~ 43200,默认 3600 }) res.status(r.status).json(await r.json()) // { token, expiresAt } }) ``` 如果后端也用 Node,可以改用 SDK:`new AgentClient({ baseUrl, apiKey }).issueBrowserToken(3600)`。 ## 3. 前端:安装 SDK SDK 由 AgentServer 自己提供,三个包需要一起安装: ```bash pnpm add https://agent.zenpower.cc/sdk/agent-server-shared-0.5.0.tgz \ https://agent.zenpower.cc/sdk/agent-server-client-0.5.0.tgz \ https://agent.zenpower.cc/sdk/agent-server-chat-ui-0.5.0.tgz ``` | 包 | 内容 | |---|---| | `@agent-server/shared` | API 与事件的类型、常量 | | `@agent-server/client` | `AgentClient`:会话、消息、SSE 订阅(断线续传)、交互回复、上传下载;`BotClient`:机器人工作组(§8) | | `@agent-server/chat-ui` | 对话组件 `` 与状态 `useChatSession`;工作组组件 `` 与状态 `useTeamSession`(依赖 `vue@^3.5`) | 最新版本号见 `https://agent.zenpower.cc/sdk/index.json`。升级时修改上面 URL 中的版本号即可,旧版本会一直保留。 ## 4. 前端:使用对话组件 ```ts // main.ts:只引入设计变量和组件自身的样式,不会改动平台页面的其他样式 import '@agent-server/chat-ui/style.css' ``` ```vue ``` `` 会占满父元素的高度,请给它一个确定高度的容器。 | 属性 / 插槽 / 事件 | 说明 | |---|---| | `client`、`session` | 必填:`AgentClient` 实例和 `useChatSession()` 的返回值 | | `readonly` | 只读回放:不显示输入框,不能回复审批 | | `placeholder` | 输入框提示文字 | | `#toolbar` 插槽 | 输入框底部工具栏,可放模型选择等 | | `#empty` 插槽 | 没有消息时显示的内容 | | `@open-file` | 点击文件卡片时触发(`{ fileId, path, mime, size }`),用于自己的预览。不监听时直接下载文件 | 组件已经处理了: - 流式文本与思考过程 - 工具调用卡片 - 审批与提问卡片 - 上传附件(点击、拖拽、粘贴截图) - Agent 产出文件的下载 - 中断运行 - 断线自动重连 ## 5. 常用接口 ```ts await client.profiles() // 本平台可用的 Profile await client.sessions.list({ limit: 30 }) // 本平台的会话(分页:nextCursor) await client.sessions.update(id, { title: '…', archived: true }) await client.sessions.remove(id) // 删除会话及其工作区 await client.sessions.files(id) // 会话中的全部文件 await client.usage(30) // 本平台最近 30 天的用量 ``` 不使用 ``、自己渲染界面时: - 用 `client.subscribe(sessionId, { onEvent })` 订阅事件 - 用 `client.sessions.send(id, { text, attachments })` 发送消息 - 用 `client.interactions.reply(itxId, …)` 回复审批和提问 事件类型见 [design/04-api.md](design/04-api.md)。`useChatSession` 的 `state` 也可以单独使用,它是已经整理好的时间线。 ## 5.1 定时任务 用户在对话中说"每个工作日早上 9 点帮我出日报",AI 会创建定时任务:对话中出现确认卡片,用户确认后生效,之后到点由 AgentServer 自动往同一个会话(或工作组)发出指令并执行,结果留在时间线中。设计见 [design/14-schedules.md](design/14-schedules.md)。 - ``、`` 已经处理了确认卡片,并在顶部显示定时任务栏(可以暂停、恢复、删除),平台不需要额外开发。 - 自己渲染界面时: ```ts await client.sessions.schedules(sessionId) // 会话的定时任务 await client.schedules.setStatus(scheduleId, 'paused') // 暂停或恢复('active') await client.schedules.remove(scheduleId) await client.schedules.runs(scheduleId) // 执行记录 await bots.teams.schedules(teamId) // 工作组的定时任务(BotClient) ``` - 定时执行时没有人在场:需要审批的操作会在等待超时后被拒绝。希望定时任务稳定执行的场景,使用"自动执行"或"只读"的 Profile。 - 每次执行有时长和成本上限(后台"设置 › 定时任务"),超出成本上限时定时任务自动暂停。 ## 5.2 后台调用(外部工具的长调用) 接入的外部工具(MCP 服务)中执行时间很长的调用(导出、跑批等)会转到后台:AI 先告诉用户"已提交",结束这一轮;服务端继续等待,完成后把结果作为一条新消息发到会话(或工作组中发起调用的机器人),AI 收到后继续处理。设计见 [design/15-background-calls.md](design/15-background-calls.md)。 - ``、`` 顶部会显示"后台执行中",可以取消;结果到达后自动出现在时间线中。 - 自己渲染界面时:`client.sessions.backgroundCalls(id)`、`client.backgroundCalls.cancel(callId)`;工作组用 `bots.teams.backgroundCalls(teamId)`、`bots.teams.cancelBackgroundCall(teamId, callId)`。 - 超过多少秒转后台、最长多久,在后台"设置 › 后台调用"中调整;执行时间一定很长的工具,可以在"MCP 服务"中标记为"后台执行"。 ## 6. 外观 - 亮色/暗色默认跟随系统;在 `` 上设置 `data-theme="light"` 或 `"dark"` 可以强制指定。 - 配色来自 `--app-*` CSS 变量,平台可以在自己的样式里覆盖,例如 `:root { --app-button-background: #2563eb; }`。完整列表见 `@agent-server/ui-tokens` 的 `variables.css`。 - 组件的原子类已经编译并隔离在组件内部,平台不需要在 UnoCSS 中扫描 SDK,也不需要引入 AgentServer 的 UnoCSS 预设。 ## 7. 常见问题 | 现象 | 原因与处理 | |---|---| | 浏览器控制台报 CORS 错误 | 平台地址没有加入"设置 › 安全 › CORS 来源",或协议、端口写得不一致 | | 所有请求都返回 401 `invalid_token` | 令牌过期后刷新失败:检查平台的 `/api/agent-token` 接口,以及 Agent Key 是否已被停用 | | 403 `profile_not_allowed` | 创建会话时指定的 Profile 不在该 Key 的可用列表中 | | 流式输出一段一段地出现,或很久才出现 | 平台与 AgentServer 之间有反向代理在缓冲响应:对 `/v1/sessions/*/stream` 关闭缓冲(nginx:`proxy_buffering off`) | | 安装时报找不到 `@agent-server/shared` | 三个包要用 URL 一起安装(它们是对等依赖,不在 npm 仓库中) | | Bot API 返回 401 | Agent Key 和 Bot Key 互不通用:`/bot/v1` 要用 Bot Key 换的令牌(`/bot/v1/browser-tokens`) | ## 8. 机器人工作组(Bot API) 机器人工作组让多个机器人在一个"群"里协作:成员(平台的用户)发消息,组长理解需求、开任务并把子任务分派给组员,组员完成后组长汇总交付。设计见 [design/13-bot-teams.md](design/13-bot-teams.md),完整接口见后台"API 协议 › Bot API"。 接入方式与单个助手相同,只是换成 Bot Key 和 `/bot/v1`: 1. **后台准备**:在"机器人"页面配置机器人(组长 / 组员),在 **API Keys › Bot Keys** 签发 Bot Key 并勾选这个平台可以使用的机器人。CORS 来源与 §1 相同。 2. **平台后端**:用 Bot Key 换浏览器令牌,接口是 `POST /bot/v1/browser-tokens`,参数和返回值与 §2 相同。 3. **前端**: ```vue ``` `` 已经处理了: - 消息时间线 - 输入框中的 @ 选择 - 组长的任务与子任务进度 - 超出限额时的"继续 / 取消" - 审批卡片(含组长申请加入机器人) - 文件卡片与上传 - 正在工作的机器人和"停止" - 断线续传 | 属性 / 事件 | 说明 | |---|---| | `client`、`session` | 必填:`BotClient` 实例和 `useTeamSession()` 的返回值 | | `readonly` | 只读:不显示输入框,不能处理审批和限额 | | `placeholder` | 输入框提示文字 | | `@open-file` | 同 `` | 常用接口: ```ts await bots.teams.list({ status: 'active' }) // 本平台的工作组 await bots.teams.send(id, { text: '…', mentions: [botId] }) // @ 组员时只交给它,不 @ 时交给组长 await bots.teams.pause(id) / resume(id) / close(id) await bots.teams.addBot(id, botId) / removeBot(id, botId) await bots.teams.jobs(id) // 任务与子任务 await bots.teams.usage(id) // 按机器人、任务、子任务统计的用量 await bots.teams.resetSession(id, botId) // 让机器人开始新的会话(组长在当前任务结束后) ``` 说明: - 一个工作组只有一个成员(`human`),平台用户之间不共享工作组。 - 限额(子任务数、组长轮数、时长、成本)以 AgentServer 的系统设置为上限,创建工作组时只能调低。 - 机器人的会话上下文过长或闲置太久时会自动开始新的会话(组长在两项任务之间换),之前的工作从共享目录 `.team/jobs/` 下的任务纪要了解。 - 替身组员、逐步执行是后台 Bot Playground 的调试功能,平台不能设置。