// Bot API 客户端(/bot/v1,docs/design/13-bot-teams.md §9)。使用 Bot Key 或 Bot 浏览器令牌,与 AgentClient 的 Key 互不通用 import type { BotCard, BrowserTokenDto, CreateTeamBody, FileDto, InteractionResponse, PatchTeamBody, SendTeamMessageBody, StubTaskBody, TeamDetailDto, TeamDto, TeamEvent, TeamJobsDto, TeamMessage, TeamStatus, TeamUsageDto, } from '@agent-server/shared' import { AgentApiError, HttpClient, query, type StreamHandlers } from './http' export class BotClient extends HttpClient { /** 平台后端调用(需要 Bot Key):签发交给浏览器使用的短期令牌 */ issueBrowserToken = (ttlSeconds?: number) => this.request('POST', '/bot/v1/browser-tokens', ttlSeconds === undefined ? {} : { ttlSeconds }) /** 这个 Key 可以使用的机器人及其能力卡片 */ bots = () => this.request('GET', '/bot/v1/bots') teams = { create: (body: CreateTeamBody) => this.request('POST', '/bot/v1/teams', body), list: (q: { scope?: 'all'; status?: TeamStatus; limit?: number } = {}) => this.request('GET', `/bot/v1/teams${query(q)}`), get: (id: string) => this.request('GET', `/bot/v1/teams/${id}`), /** 标题、限额;debug 只有后台可以设置 */ update: (id: string, body: PatchTeamBody) => this.request('PATCH', `/bot/v1/teams/${id}`, body), remove: (id: string) => this.request('DELETE', `/bot/v1/teams/${id}`), pause: (id: string) => this.request('POST', `/bot/v1/teams/${id}/pause`), resume: (id: string) => this.request('POST', `/bot/v1/teams/${id}/resume`), close: (id: string) => this.request('POST', `/bot/v1/teams/${id}/close`), reopen: (id: string) => this.request('POST', `/bot/v1/teams/${id}/reopen`), addBot: (id: string, botId: string) => this.request('POST', `/bot/v1/teams/${id}/bots`, { botId }), removeBot: (id: string, botId: string) => this.request('DELETE', `/bot/v1/teams/${id}/bots/${botId}`), interruptBot: (id: string, botId: string) => this.request<{ ok: true }>('POST', `/bot/v1/teams/${id}/bots/${botId}/interrupt`), /** 让机器人开始新的会话(组长在当前任务结束后) */ resetSession: (id: string, botId: string) => this.request<{ ok: true }>('POST', `/bot/v1/teams/${id}/bots/${botId}/reset-session`, {}), send: (id: string, body: SendTeamMessageBody) => this.request('POST', `/bot/v1/teams/${id}/messages`, body), messages: (id: string, after = 0) => this.request<{ items: TeamEvent[]; lastSeq: number }>('GET', `/bot/v1/teams/${id}/messages?after=${after}`), events: (id: string, after = 0) => this.request<{ items: TeamEvent[]; lastSeq: number }>('GET', `/bot/v1/teams/${id}/events?after=${after}`), files: (id: string) => this.request('GET', `/bot/v1/teams/${id}/files`), upload: (id: string, files: Blob[] | File[]) => { const form = new FormData() for (const f of files) form.append('file', f, (f as File).name ?? 'upload') return this.request('POST', `/bot/v1/teams/${id}/files`, form) }, reply: (id: string, interactionId: string, body: InteractionResponse) => this.request<{ ok: true }>('POST', `/bot/v1/teams/${id}/interactions/${interactionId}`, body), jobs: (id: string) => this.request('GET', `/bot/v1/teams/${id}/jobs`), cancelJob: (id: string, jobId: string) => this.request<{ ok: true }>('POST', `/bot/v1/teams/${id}/jobs/${jobId}/cancel`), continueJob: (id: string, jobId: string) => this.request<{ ok: true }>('POST', `/bot/v1/teams/${id}/jobs/${jobId}/continue`), usage: (id: string) => this.request('GET', `/bot/v1/teams/${id}/usage`), /** 仅后台:替身组员的子任务结果 */ stubResult: (id: string, taskId: string, body: StubTaskBody) => this.request<{ ok: true }>('POST', `/bot/v1/teams/${id}/tasks/${taskId}/stub`, body), /** 仅后台:逐步执行时放行一步 */ continueStep: (id: string, stepId: string) => this.request<{ ok: true }>('POST', `/bot/v1/teams/${id}/steps/${stepId}/continue`), } /** 文件下载地址(同源 Cookie 鉴权时可直接用于 /) */ fileUrl(fileId: string, inline = false) { return `${this.opts.baseUrl}/bot/v1/files/${fileId}${inline ? '?inline=1' : ''}` } /** 带鉴权头下载文件为 Blob */ async fileBlob(fileId: string): Promise { const res = await this.send(`/bot/v1/files/${fileId}?inline=1`, {}) if (!res.ok) throw new AgentApiError(res.status, 'http_error', `HTTP ${res.status}`) return res.blob() } /** 订阅工作组事件流;断线自动重连并从最后收到的 seq 续传。返回取消函数 */ subscribe(teamId: string, handlers: StreamHandlers, opts: { after?: number } = {}): () => void { return this.stream(`/bot/v1/teams/${teamId}/stream`, handlers, opts.after ?? 0) } }