@blade-hq/agent-client 2608.0.8 → 2608.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,12 +2,11 @@
2
2
 
3
3
  Blade Agent 的框架无关客户端。浏览器和 Node.js 都能用;用 Vue、Svelte 或自建 UI 的团队直接用这个包,React 团队一般用上层的 `@blade-hq/agent-react`。
4
4
 
5
- 它做四件事:
5
+ 它做三件事:
6
6
 
7
7
  1. **实时会话**(`AgentSession`):把 Socket.IO 协议、历史加载、流式合流、断线重连全部封装掉,你只面对"状态快照 + 动作 + 事件"。
8
8
  2. **登录**:`client.auth.login()` 弹窗授权,用户点一下"许可授权"就拿到访问令牌,不用手工复制粘贴。
9
9
  3. **REST**:类型化会话和模型目录(`client.sessions.*`、`client.models.list()`)。其他长尾接口对照 Swagger 用原生 `fetch` + `client.token` 调用。
10
- 4. **部署端点**:从同源 `config.json` 读取其他 Blade 服务的公开地址,不根据主机名和固定端口猜测拓扑。
11
10
 
12
11
  ```bash
13
12
  npm install @blade-hq/agent-client
@@ -48,27 +47,6 @@ const client = new BladeClient({
48
47
  })
49
48
  ```
50
49
 
51
- ## 部署端点
52
-
53
- Blade 平台前端需要跳转其他服务时,读取当前 origin 的公开配置:
54
-
55
- ```ts
56
- import { loadPlatformEndpoints, resolveServiceUrl } from "@blade-hq/agent-client"
57
-
58
- const endpoints = await loadPlatformEndpoints()
59
- const hubUrl = resolveServiceUrl(endpoints, "hub", "/skills/42")
60
- if (hubUrl) window.open(hubUrl)
61
- ```
62
-
63
- `loadPlatformEndpoints()` 请求 `config.json`,超过 3 秒、网络失败或配置非法时返回空配置;
64
- 调用方应隐藏对应入口。文件只能放浏览器可访问的公开地址,不能写 Docker 服务名、令牌或
65
- 其他内部配置。应用部署在子路径时,通过 `baseUrl` 显式传入部署根路径。
66
- `resolveServiceUrl()` 的 `path` 只接受服务内相对路径;绝对 URL、反斜杠和越出服务
67
- base path 的路径返回 `null`。
68
-
69
- 公开类型为 `PlatformEndpoints`、`PlatformServiceName`、`LoadPlatformEndpointsOptions`;
70
- 需要同步初始化时可直接使用 `EMPTY_PLATFORM_ENDPOINTS`。
71
-
72
50
  ## BladeClient
73
51
 
74
52
  ### 构造
@@ -98,6 +76,15 @@ await client.auth.getProviders() // 服务端支持的登录方式
98
76
 
99
77
  `login()` 失败时的错误都带中文原因:弹窗被拦截、窗口被关闭、超时。
100
78
 
79
+ 令牌要留在自己的服务端时,浏览器只把授权回调拿到的一次性 code 交给后端,由后端换:
80
+
81
+ ```ts
82
+ // ExchangeCodeParams -> ExchangeCodeResult
83
+ const { access_token } = await client.auth.exchangeCode({ code, state, clientOrigin })
84
+ ```
85
+
86
+ `clientOrigin` 要和发起授权时用的 origin 一致;服务端调用会自动补 `Origin` 头。
87
+
101
88
  ## 会话:client.sessions
102
89
 
103
90
  ### 实时对话(推荐入口)
@@ -152,6 +139,73 @@ await client.sessions.deleteSession(id)
152
139
  await client.sessions.getSessionTurns(id) // 历史消息(投影格式,与实时流同构)
153
140
  ```
154
141
 
142
+ ### 会话回放(演示 / 彩排)
143
+
144
+ 拿一个已经聊完的会话当素材,重现当时的回复和工具调用,**完全不调用模型**。
145
+ 用来做演示和彩排:离线可跑、不花钱、不会临场翻车。
146
+
147
+ **它不是把现有会话切成回放模式,而是派生一个新会话**,源会话原样不动:
148
+
149
+ ```ts
150
+ // 1. 能不能拿它当素材(有并行子智能体、运行快照不完整的不行)
151
+ const { supported, reason } = await client.sessions.getReplayPreview(sourceId)
152
+ if (!supported) return show(reason) // reason 可直接展示给用户
153
+
154
+ // 2. 派生回放会话——这一步才是"进入回放",返回的是一个全新的 session_id
155
+ const { session_id: replayId } = await client.sessions.startReplaySession(sourceId)
156
+ const fast = await client.sessions.startReplaySession(sourceId, 2) // 或显式 1 | 2 | 5
157
+
158
+ // 3. 连上它,一出生就带着 replay 状态
159
+ const session = await client.sessions.connect(replayId)
160
+ session.getState().replay
161
+ // → { isReplay: true, speed: 5, sourceSessionId: sourceId }
162
+ ```
163
+
164
+ 不传倍速时用 `DEFAULT_REPLAY_SPEED`;所有创建入口都该用它,免得同一个源对话
165
+ 从不同入口开出来速度不一样。
166
+
167
+ **连上之后不会自动播——它跟用户对台词**(下面的 `session` 都是第 3 步连上的**回放会话**):
168
+
169
+ ```ts
170
+ // 源会话当初第一句问的是"帮我查下当前目录"
171
+ await session.send("帮我查下当前目录") // 对得上:按倍速重现当时的回复和工具调用
172
+ await session.send("今天天气怎么样") // 对不上:抛 replayMismatch 等你决定(见下)
173
+ ```
174
+
175
+ **回放中的状态和动作**都挂在会话上,和 `send()` 同级:
176
+
177
+ ```ts
178
+ session.getState().replay // { isReplay, speed, sourceSessionId } | null(不是回放会话)
179
+ session.getState().viewerRole // "viewer" 表示只读,改不动回放
180
+ await session.setReplaySpeed(2)
181
+ await session.exitReplay() // 退出回放,之后的对话真的运行
182
+ ```
183
+
184
+ 改回放是 owner 专属(服务端 `PATCH` 走 owner 校验),只读身份下这两个动作是 no-op。
185
+
186
+ `replay` 是 `SessionState` 的一部分,跟着快照订阅走——Vue 直接接 `shallowRef` 即可,
187
+ 不用自己拉状态:
188
+
189
+ ```ts
190
+ const state = shallowRef(session.getState()) // session = connect(replayId) 的返回值
191
+ session.subscribe(() => (state.value = session.getState()))
192
+ // 模板里 state.value.replay?.isReplay / state.value.replay?.speed
193
+ ```
194
+
195
+ 连接会话时会一并拉好初始回放状态,所以第一帧就知道自己在不在回放。
196
+
197
+ **输入冲突**:用户说的话和录制内容对不上时,服务端会推 `replayMismatch`,必须在事件里给出决定:
198
+
199
+ ```ts
200
+ session.on("replayMismatch", ({ expectedMessage, actualMessage, respond }) => {
201
+ respond("keep_replay") // 按录制内容继续
202
+ // respond("continue_replay") // 从这里开始真的运行
203
+ })
204
+ ```
205
+
206
+ `replayMismatch` **只交给最先注册的处理器**;一个都没注册时默认 `continue_replay`
207
+ (静默转为真实运行)。React 应用可直接用 `@blade-hq/agent-react` 的 `useReplay()`。
208
+
155
209
  ### 工作区文件
156
210
 
157
211
  ```ts
@@ -173,10 +227,26 @@ await chat.downloadFile("输出/简历.md", "简历.md") // 浏览器下载
173
227
  ### 模型目录
174
228
 
175
229
  ```ts
176
- const { default: defaultModel, models } = await client.models.list()
177
- // models: Array<{ id, label }>
230
+ const { default: defaultModel, models, baseUrl, defaultServiceModel } = await client.models.list()
231
+ // models: Array<{ id, label, serviceModelId }>
232
+ // baseUrl: 平台默认模型服务的 OpenAI 兼容地址,自建应用带用户令牌可直接调;
233
+ // 各部署端口不同,别写死,老版本 Server 返回空串。
234
+ ```
235
+
236
+ **建会话选模型用 `default` / `id`,直接调 `baseUrl` 用 `defaultServiceModel` / `serviceModelId`。**
237
+ 两套名字不通用:`id` 是平台内部标识,平台接了多个模型服务时形如
238
+ `provider-xxx::deepseek-v4-flash`,拿它去调 `baseUrl` 只会得到「模型不存在」。
239
+ `serviceModelId` 为空串表示这个模型不在 `baseUrl` 那个服务上,做模型选择器时按它过滤。
240
+
241
+ ```ts
242
+ // 自己调模型服务
243
+ const body = { model: defaultServiceModel, messages, stream: true }
178
244
  ```
179
245
 
246
+ `baseUrl` 是**服务端视角**的地址。平台常配成 `http://127.0.0.1:30000/v1`——从自己后端转发
247
+ 没问题,直接交给浏览器就指向用户自己的机器了。默认形态是后端透传(密钥本来也不该进浏览器),
248
+ 浏览器直连只适合这个地址对浏览器同样可达的场景。
249
+
180
250
  ## AgentSession
181
251
 
182
252
  一个会话的实时状态机。**状态归属实例**:同一页面建多个会话互不干扰。
@@ -193,6 +263,8 @@ chat.getState()
193
263
  // mode, "planning" | "executing" | null
194
264
  // connection, 连接状态:"connected" | "connecting" | "reconnecting" | "disconnected"
195
265
  // errorMessage, 最近一次运行错误
266
+ // replay, 回放状态:{ isReplay, speed, sourceSessionId } | null
267
+ // viewerRole, "owner" | "viewer" | null(只读身份改不动会话)
196
268
  // turns, askAnswers, agentLoops, activeCompaction 进阶字段
197
269
  // }
198
270
 
@@ -387,6 +459,29 @@ function Message({ message }: { message: ChatMessage }) {
387
459
  }
388
460
  ```
389
461
 
462
+ ### 让用户挑一个技能来做事
463
+
464
+ 自己实现技能选择器时,用 `transformSlashCommand` 把选中的技能翻译成智能体能执行的一段话:
465
+
466
+ ```ts
467
+ import { transformSlashCommand, type SkillMentionAvailability } from "@blade-hq/agent-client"
468
+
469
+ transformSlashCommand("org/data-analysis", "分析这份季度报表")
470
+ // "请使用 org/data-analysis skill 完成任务\n分析这份季度报表"
471
+ ```
472
+
473
+ 第三个参数说明这个技能眼下能不能直接用。技能装没装、加载没加载,用户不需要知道,但智能体需要——传进来之后它会被翻译成对应的 CLI 步骤:
474
+
475
+ ```ts
476
+ // Blade Hub 上已安装、但当前会话还没加载:要求先 use
477
+ transformSlashCommand(skillId, prompt, { local: false, installed: true })
478
+
479
+ // Blade Hub 上还没安装:要求先 install 再 use
480
+ transformSlashCommand(skillId, prompt, { local: false, installed: false })
481
+ ```
482
+
483
+ 不传第三个参数时按本地已有处理,和不带这个参数的老用法结果一致。
484
+
390
485
  ## 常见问题
391
486
 
392
487
  | 现象 | 原因与解法 |
@@ -408,9 +503,10 @@ function Message({ message }: { message: ChatMessage }) {
408
503
  - **声明式会话**:`SessionDefinition`、`SolutionDefinition`、`SkillDefinition`、`SessionConfig`、`TextFile`、`SessionSetupError`、`SessionSetupStage`
409
504
  - **模型目录**:`ModelsResource`、`ModelCatalog`、`ModelOption`
410
505
  - **会话资源(REST)**:`SessionsResource`、`CreateSessionRequest`、`ImportSessionOptions`、`AppCliDefinition`、`AppCliAttachment`、`AttachAppOptions`、`PaginatedSessionsResult`、`SessionHistory`、`SessionContextStats`、`ShareLinkResult`、`FileEntry`、`UploadFileEntry`、`UploadFilesOptions`、`SessionProfile`、`SessionDetail`、`SessionInfo`、`SessionStatus`、`SessionPortMapping`、`ModeId`、`TemplateId`、`PrimarySkillSnapshot`、`PrimarySkillParallelMode`
506
+ - **会话回放**:`ReplayState`、`ReplaySpeed`、`ReplayPreview`、`ReplaySnapshot`、`toReplaySnapshot`、`DEFAULT_REPLAY_SPEED`
411
507
  - **会话状态机**:`SessionHub`、`SessionState`、`SendOptions`、`ConnectionStatus`、`AskUserAnswerData`、`AgentLoopInfo`、`ActiveCompactionState`、`createInitialSessionState`、`AgentSessionEventName`
412
508
  - **页面协作**:`EmbeddedChat`、`EmbeddedChatOptions`、`CommandHandler`、`CommandEnvelope`、`InboundAction`、`InboundEnvelope`、`isCommandEnvelope`、`isInboundEnvelope`
413
- - **消息与投影协议**:`MessageContent`、`MessageContentPart`、`TextContentPart`、`ImageUrlContentPart`、`FileContentPart`、`ToolCallInfo`、`ToolBridgeContent`、`CompactionInfo`、`MemoryRefInfo`、`ArchivedFileInfo`、`ArchivedToolCallInfo`、`TurnProjection`、`ContentBlock`、`PatchEnvelope`、`MemoryRef`、`buildMessageContent`、`normalizeMessageContent`、`isHiddenInternalMessage`、`transformSlashCommand`、`extractTextAttachments`、`ParsedTextAttachment`、`ParsedTextContext`
509
+ - **消息与投影协议**:`MessageContent`、`MessageContentPart`、`TextContentPart`、`ImageUrlContentPart`、`FileContentPart`、`ToolCallInfo`、`ToolBridgeContent`、`CompactionInfo`、`MemoryRefInfo`、`ArchivedFileInfo`、`ArchivedToolCallInfo`、`TurnProjection`、`ContentBlock`、`PatchEnvelope`、`MemoryRef`、`buildMessageContent`、`normalizeMessageContent`、`isHiddenInternalMessage`、`transformSlashCommand`、`SkillMentionAvailability`、`extractTextAttachments`、`ParsedTextAttachment`、`ParsedTextContext`
414
510
  - **Solution / 任务协议**:`Solution`、`SolutionAppField`、`SolutionAppState`、`SolutionAppUiConfig`、`SolutionRef`、`PublishedSolutionRef`、`ExistingSolutionRef`、`PreparedSolution`、`PreparedSolutionAsset`、`LayoutType`、`BizRole`、`TaskStatus`、`BackgroundTask`、`BackgroundTaskStopResult`
415
511
  - **Headless**:`HeadlessResource`、`RunOptions`、`RunResult`、`RunTrace`
416
512
  - **低层通道(apps/web 等高级集成)**:`createSocket`、`CreateSocketOptions`、`TypedSocket`、`AsrAudioPayload`、`ClientProjectionBuilder`、`RawEvent`
package/dist/index.d.ts CHANGED
@@ -2,8 +2,6 @@ export { BladeClient } from "./blade-client";
2
2
  export type { BladeClientOptions, UploadProgress } from "./blade-client";
3
3
  export { BladeApiError } from "./rest";
4
4
  export type { LoginOptions, LoginResult, TokenStorageMode } from "./auth-login";
5
- export { EMPTY_PLATFORM_ENDPOINTS, loadPlatformEndpoints, resolveServiceUrl, } from "./platform-endpoints";
6
- export type { LoadPlatformEndpointsOptions, PlatformEndpoints, PlatformServiceName, } from "./platform-endpoints";
7
5
  export { SDK_NAME, SDK_VERSION } from "./version";
8
6
  export { AgentSession } from "./session/agent-session";
9
7
  export type { AttachAppOptions, SendOptions } from "./session/agent-session";
@@ -11,14 +9,14 @@ export { SessionHub } from "./session/hub";
11
9
  export type { AgentSessionEvents, AgentSessionEventName } from "./session/events";
12
10
  export { SessionSetupError } from "./session/definition";
13
11
  export type { SessionConfig, SessionDefinition, SessionSetupStage, SkillDefinition, TextFile, SolutionDefinition, } from "./session/definition";
14
- export { createInitialSessionState } from "./session/state";
15
- export type { AskUserAnswerData, AgentLoopInfo, ActiveCompactionState, ConnectionStatus, SessionState, } from "./session/state";
12
+ export { createInitialSessionState, toReplaySnapshot } from "./session/state";
13
+ export type { AskUserAnswerData, AgentLoopInfo, ActiveCompactionState, ConnectionStatus, ReplaySnapshot, SessionState, } from "./session/state";
16
14
  export { connectEmbedded } from "./commands/embedded";
17
15
  export type { EmbeddedChat, EmbeddedChatOptions } from "./commands/embedded";
18
16
  export type { CommandHandler } from "./commands/registry";
19
17
  export type { CommandEnvelope, InboundAction, InboundEnvelope } from "./commands/protocol";
20
18
  export { isCommandEnvelope, isInboundEnvelope } from "./commands/protocol";
21
- export type { AuthResource, ProvidersResponse, UserInfo } from "./resources/auth";
19
+ export type { AuthResource, ExchangeCodeParams, ExchangeCodeResult, ProvidersResponse, UserInfo, } from "./resources/auth";
22
20
  export type { HeadlessResource } from "./resources/headless";
23
21
  export { ModelsResource } from "./resources/models";
24
22
  export type { ModelCatalog, ModelOption } from "./resources/models";
@@ -26,10 +24,11 @@ export type { SessionsResource } from "./resources/sessions";
26
24
  export type { CreateSessionRequest, AppCliAttachment, AppCliDefinition, FileEntry, ImportSessionOptions, PaginatedSessionsResult, SessionContextStats, SessionHistory, ShareLinkResult, UploadFileEntry, UploadFilesOptions, } from "./resources/sessions";
27
25
  export type { ArchivedFileInfo, ArchivedToolCallInfo, ChatMessage, CompactionInfo, FileContentPart, ImageUrlContentPart, MemoryRefInfo, MessageContent, MessageContentPart, TextContentPart, ToolBridgeContent, ToolCallInfo, } from "./schemas/message";
28
26
  export { buildMessageContent, contentPreview, extractTextAttachments, getFileParts, getImageParts, getTextContent, groupMessagesByLoop, isHiddenInternalMessage, normalizeMessageContent, transformSlashCommand, } from "./schemas/message-utils";
29
- export type { ParsedTextAttachment, ParsedTextContext } from "./schemas/message-utils";
27
+ export type { ParsedTextAttachment, ParsedTextContext, SkillMentionAvailability, } from "./schemas/message-utils";
30
28
  export type { ContentBlock, MemoryRef, PatchEnvelope, TurnProjection } from "./schemas/projection";
29
+ export { DEFAULT_REPLAY_SPEED } from "./schemas/session";
31
30
  export { SessionInfo, SessionStatus } from "./schemas/session";
32
- export type { ModeId, PrimarySkillParallelMode, PrimarySkillSnapshot, SessionDetail, SessionPortMapping, PublishedSolutionRef, TemplateId, } from "./schemas/session";
31
+ export type { ModeId, PrimarySkillParallelMode, PrimarySkillSnapshot, ReplayPreview, ReplaySpeed, ReplayState, SessionDetail, SessionPortMapping, PublishedSolutionRef, TemplateId, } from "./schemas/session";
33
32
  export { LayoutType } from "./schemas/solution";
34
33
  export type { BizRole, Solution, SolutionAppField, SolutionAppState, SolutionAppUiConfig, } from "./schemas/solution";
35
34
  export { Task, TaskStatus } from "./schemas/task";