@blade-hq/agent-client 2612.0.0-beta.43 → 2612.0.0-beta.45

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
@@ -452,6 +452,47 @@ await client.computers.setEnabled(sessionId, computer.id, false) // 停用
452
452
 
453
453
  `ComputersResource` 是 `client.computers` 的类型。
454
454
 
455
+ ### 手机:client.phones
456
+
457
+ 用户接进来的 Android 手机。手机在这台工作环境里就是一台 adb 设备,智能体可以直接
458
+ 用 `adb` 命令操作它。和一键可用的电脑不同,手机要先在设置页拿到一把**接入密钥**
459
+ 填进手机 App,App 连上来之后设备自动登记。
460
+
461
+ ```ts
462
+ const { phones } = await client.phones.listAccount()
463
+ // PhoneList → { phones: PhoneDevice[] }
464
+ // PhoneDevice: { id, label, serial, model, android_version, created_at,
465
+ // last_seen_at, online, adb_online, enabled, session_id }
466
+
467
+ await client.phones.setEnabled(sessionId, phone.id, true) // 本次会话可以用它
468
+ await client.phones.setEnabled(sessionId, phone.id, false) // 停用
469
+ ```
470
+
471
+ `online` 和 `adb_online` 是**两件独立的事**,会单独坏:
472
+
473
+ - `online`:手机此刻连着后端。连上就算,不等沙盒。
474
+ - `adb_online`:沙盒里的 adb 此刻能对它执行命令。需要 adb 认证通过。
475
+
476
+ 手机连着但沙盒里没装 adb、或者认证没过,`online` 仍然是 true 而 `adb_online` 是
477
+ false。合成一个「离线」会让人去排查网络,而问题在沙盒。
478
+
479
+ `PhoneState` 是把这两个字段折成 UI 状态的唯一出处(`phoneState(phone)`),不要
480
+ 在调用方重新拼一遍判断。
481
+
482
+ ```ts
483
+ const key = await client.phones.getKey()
484
+ // PhoneAccessKey → { key, hub_url, connector_url }
485
+
486
+ await client.phones.resetKey() // 换一把新密钥并作废旧密钥
487
+ await client.phones.revokeKey() // 作废,之后手机重连一律被拒
488
+ ```
489
+
490
+ `key` 是以 `pk_` 开头的长期凭据,没作废就一直有效。`hub_url` 是服务器地址(协议+
491
+ 主机+端口),`connector_url` 是手机 App 的下载地址,管理员可以配成自己的静态文件。
492
+
493
+ `PhonesResource` 是 `client.phones` 的类型,`PhoneDevice` / `PhoneAccessKey` /
494
+ `PhoneList` / `PhoneHealth` / `PhonePipeline` / `PhoneState` 是配套的公开类型。
495
+
455
496
  ## AgentSession
456
497
 
457
498
  一个会话的实时状态机。**状态归属实例**:同一页面建多个会话互不干扰。
@@ -751,7 +792,7 @@ chat.renderers = {
751
792
  - **页面协作**:`CommandHandler`、`BladeChatMountRenderers`、`MountRenderer`、`MountRendererObject`、`MountResult`、`MountHandle`
752
793
  - **会话资源(REST)**:`SessionsResource`、`CreateSessionRequest`、`McpSourceConfig`、`PaginatedSessionsResult`、`SessionInfo`、`SessionStatus`、`SessionDetail`、`DEFAULT_REPLAY_SPEED`、`ReplaySpeed`、`ReplayState`、`SessionTurnsPage`、`SessionTurnsQuery`、`FileEntry`、`UploadFileEntry`、`UploadFilesOptions`、`AppCliDefinition`、`AppCliAttachment`
753
794
  - **模型目录**:`ModelsResource`、`ModelCatalog`、`ModelOption`
754
- - **沙盒脚本**:`SandboxResource`、`SandboxScriptRequest`、`SandboxScriptResult`。`client.sandbox.execute({ script, cwd, timeout })` 在当前登录用户的沙盒执行脚本,无需创建智能体会话;`cwd` 使用绝对路径,`timeout` 单位为秒,默认及最大值为 120。结果包含有界输出、可为空的退出码、超时标志和错误信息;无法启动或无法确认停止时应检查 `error`,不能仅凭退出码判断成功。
795
+ - **沙盒脚本**:`SandboxResource`、`SandboxScriptRequest`、`SandboxScriptResult`。`client.sandbox.execute({ script, cwd, timeout })` 在当前登录用户的沙盒执行脚本,无需创建智能体会话;`cwd` 使用绝对路径,`timeout` 单位为秒,默认及最大值为 120。结果包含合并诊断输出 `output`、独立标准输出 `stdout` 及其截断标志 `stdout_truncated`、可为空的退出码、超时标志和错误信息;无法启动或无法确认停止时应检查 `error`,不能仅凭退出码判断成功。
755
796
  - **远程电脑**:`ComputersResource`、`SessionComputer`、`AccountComputer`
756
797
  - **Headless**:`HeadlessResource`、`RunOptions`、`RunResult`、`RunTrace`
757
798
  - **消息**:`ChatMessage`、`MessageContent`、`MessageContentPart`、`TextContentPart`、`ImageUrlContentPart`、`FileContentPart`、`ToolCallInfo`、`WorkflowStepInfo`(固定流程运行时每步的留档,挂在 `workflow:run` 的 `ToolCallInfo.workflow_steps` 上)、`MemoryRefInfo`、`getTextContent`、`getImageParts`、`getFileParts`、`contentPreview`、`groupMessagesByLoop`、`chatErrorForDisplay`、`transformSlashCommand`、`SkillMentionAvailability`、`userFacingErrorText`
@@ -843,7 +884,9 @@ const delivery = await session.getDelivery(request.requestId)
843
884
 
844
885
  同一保存请求可重试,BA 不重复入队;同 ID 改变正文或参数会拒绝。接收结果 `accepted` 表示输入已持久化,不表示任务完成。`unknown` 表示无法确认,`not_found` 是查询未找到;不得因此生成新 ID。查询可返回队列项、队列状态和执行 ID;记录保留至会话删除。
845
886
 
846
- 此接口复用后端等待队列:空闲时自动开始,运行中等待,暂停时保留。可靠请求不能编辑或作为立即补充,但可以取消、重排和继续。支持普通文本、模式、模型、思考与推理强度;AskUser 作答、多模态、whatif 和 replay 继续使用 `send()`。需要支持 `prepared_options` 及 `chat:queue:request` 的 BA;旧服务的普通队列确认不算可靠请求回执。
887
+ 此接口复用后端持久化投递:空闲时自动开始,默认在运行中等待,暂停时保留。设置 `queueWhenRunning: false` 时补充当前运行,PA 使用此模式。可靠请求不能编辑,但可以立即补充、取消、重排和继续。支持普通文本、模式、模型、思考与推理强度;AskUser 作答、多模态、whatif 和 replay 继续使用 `send()`。需要支持 `prepared_options` 及 `chat:queue:request` 的 BA;旧服务的普通队列确认不算可靠请求回执。
888
+
889
+ `PreparedSendOptions.headless: true` 让可靠输入使用 BA 的 `submit_result` 交付契约,运行中追加仍遵循 `queueWhenRunning`。`session.getState().lastRun` 保留最近 BA 执行终态(`runId`、`status`、原始 `result`),实时与重连日志补发一致,不依赖消息分页。调用方应通过 `getDelivery(requestId).executionId` 关联本轮执行,不能把上一轮终态当成本轮结果。
847
890
 
848
891
  公开类型:`PreparedSend` 是可持久化的请求快照,`PreparedSendOptions` 是普通输入的执行参数,`DeliveryReceipt` 是接受或查询结果。
849
892
 
@@ -869,7 +912,7 @@ await persist(request)
869
912
  await session.sendPrepared(request)
870
913
  ```
871
914
 
872
- 每条消息最多 20 份资料,ID 不重复,每份正文最多 64000 个 Unicode 字符,整个资料 JSON 最多 256 KiB,超限拒绝。文件沿用指定会话的鉴权与路径解析,不接受外部 URL;仅有 `name` 的文件显示不可用。带资料的可靠消息作为独立一轮排队,不能走只传正文的「立即补充」。原生智能体目前不支持此通道。
915
+ 每条消息最多 20 份资料,ID 不重复,每份正文最多 64000 个 Unicode 字符,整个资料 JSON 最多 256 KiB,超限拒绝。文件沿用指定会话的鉴权与路径解析,不接受外部 URL;仅有 `name` 的文件显示不可用。带资料的可靠消息支持 `queueWhenRunning: false` 或「立即补充」;正文和资料一起交给当前运行,资料仍通过标准 Context 注入。原生智能体目前不支持此通道。
873
916
 
874
917
  ## 连接器管理
875
918
 
@@ -915,3 +958,13 @@ session.send("请处理新增工作", {
915
958
  ### 创建或修改连接器
916
959
 
917
960
  `client.connectors.startGeneration({ connector?, onCreated?, initiator? })` 统一创建生成会话并发送带技能引用的可见消息,返回 `{ session_id, sent }`。`connector` 传已有连接器的名称与展示名表示修改;`onCreated` 仅用于宿主登记缓存、导航。`initiator` 默认 `sdk`,内置 Web 使用 `ui`,保留历史列表和通知语义。选定连接器的名称与展示名通过标准 Context 附件传递,用户消息保持固定,包内文字不会被提升成用户指令。SDK 在发送前调用回调,并释放自身的会话引用。生成结束仍需用户到设置里的连接器正式连接。
961
+
962
+ ### Android 浏览器授权回传
963
+
964
+ BA/PA 安卓端在系统浏览器授权时使用 S256 PKCE。服务端接收原生端交回的一次性 code 后,需同时转交该请求的 verifier 和产品;普通网页弹窗不需要这两个字段:
965
+
966
+ ```ts
967
+ await client.auth.exchangeCode({ code, state, clientOrigin, codeVerifier, androidApp: "pa" })
968
+ ```
969
+
970
+ `state` 必须与发起请求时一致;PA 还须校验自身登录请求 cookie。访问令牌保存在自己的服务端,不能通过回调 URL 或网页消息转发。手机端回传及部署约定见仓库 `docs/specs/261026-android-browser-login/`。
@@ -6,6 +6,7 @@ import { type BladeFetchInit, type HttpMethod } from "./rest";
6
6
  import { AuthResource } from "./resources/auth";
7
7
  import { HeadlessResource } from "./resources/headless";
8
8
  import { ComputersResource } from "./resources/computers";
9
+ import { PhonesResource } from "./resources/phones";
9
10
  import { ModelsResource } from "./resources/models";
10
11
  import { SessionsResource } from "./resources/sessions";
11
12
  import { PluginsResource } from "./resources/plugins";
@@ -53,6 +54,8 @@ export declare class BladeClient {
53
54
  readonly auth: AuthResource;
54
55
  readonly headless: HeadlessResource;
55
56
  readonly computers: ComputersResource;
57
+ /** 用户接进来的手机:账号级清单、会话启用、画面信令。 */
58
+ readonly phones: PhonesResource;
56
59
  readonly models: ModelsResource;
57
60
  readonly sessions: SessionsResource;
58
61
  /** 按用户读取的插件目录与图标;没有会话时也能用。 */
@@ -224,7 +224,7 @@ var ToolProvider = class {
224
224
 
225
225
  // src/version.ts
226
226
  var SDK_NAME = "agent-client";
227
- var SDK_VERSION = true ? "2612.0.0-beta.43" : "1.1.1";
227
+ var SDK_VERSION = true ? "2612.0.0-beta.45" : "1.1.1";
228
228
 
229
229
  // src/auth-login.ts
230
230
  function isAuthCallbackMessage(value) {
@@ -378,6 +378,7 @@ var AuthResource = class {
378
378
  headers: { Origin: params.clientOrigin },
379
379
  body: JSON.stringify({
380
380
  code: params.code,
381
+ ...params.codeVerifier ? { code_verifier: params.codeVerifier, android_app: params.androidApp } : {},
381
382
  state: params.state,
382
383
  client_origin: params.clientOrigin
383
384
  })
@@ -3259,6 +3260,94 @@ var ComputersResource = class {
3259
3260
  }
3260
3261
  };
3261
3262
 
3263
+ // src/resources/phones.ts
3264
+ function phoneState(phone) {
3265
+ if (phone.enabled) return "enabled";
3266
+ return phone.online ? "idle" : "offline";
3267
+ }
3268
+ function sortPhones(phones) {
3269
+ return [...phones].sort((a, b) => {
3270
+ const byCreated = (a.created_at ?? "").localeCompare(b.created_at ?? "");
3271
+ if (byCreated !== 0) return byCreated;
3272
+ return a.label.localeCompare(b.label);
3273
+ });
3274
+ }
3275
+ function phoneSerialLabel(phone) {
3276
+ return phone.serial.trim();
3277
+ }
3278
+ var PhonesResource = class {
3279
+ constructor(client) {
3280
+ this.client = client;
3281
+ }
3282
+ client;
3283
+ /** 账号下的全部手机(不含"本次会话是否可用")。 */
3284
+ async listAccount() {
3285
+ return this.client.json("GET", "/api/phone/devices");
3286
+ }
3287
+ /**
3288
+ * 当前接入密钥;没有就补一个。
3289
+ *
3290
+ * **取的时候不轮换**:每次打开设置页都换钥匙会让已配好的手机集体掉线。
3291
+ */
3292
+ getKey() {
3293
+ return this.client.json("GET", "/api/phone/key");
3294
+ }
3295
+ /** 换一把新密钥(旧的立即失效)。 */
3296
+ resetKey() {
3297
+ return this.client.json("POST", "/api/phone/key/reset");
3298
+ }
3299
+ /** 作废密钥;设备记录保留,用户下次取密钥会得到一把新的。 */
3300
+ async revokeKey() {
3301
+ await this.client.fetch("DELETE", "/api/phone/key");
3302
+ }
3303
+ async remove(phoneId) {
3304
+ await this.client.fetch("DELETE", `/api/phone/devices/${encodeURIComponent(phoneId)}`);
3305
+ }
3306
+ /** 本次会话能用的手机 + 账号下的其他手机。 */
3307
+ listForSession(sessionId) {
3308
+ return this.client.json(
3309
+ "GET",
3310
+ `/api/sessions/${encodeURIComponent(sessionId)}/phones`
3311
+ );
3312
+ }
3313
+ setEnabled(sessionId, phoneId, enabled) {
3314
+ return this.client.json(
3315
+ "PUT",
3316
+ `/api/sessions/${encodeURIComponent(sessionId)}/phones/${encodeURIComponent(phoneId)}/enabled`,
3317
+ { enabled }
3318
+ );
3319
+ }
3320
+ health(sessionId, phoneId) {
3321
+ return this.client.json(
3322
+ "GET",
3323
+ `/api/sessions/${encodeURIComponent(sessionId)}/phones/${encodeURIComponent(phoneId)}/health`
3324
+ );
3325
+ }
3326
+ /**
3327
+ * WHEP 信令:POST 一个 SDP offer,拿到 answer。
3328
+ *
3329
+ * 走 `textFromInit` 而不是 `json()`:SDP 是纯文本,`json()` 会强制序列化成
3330
+ * JSON;也不能用裸 `fetch`,那会丢掉 bearer token。
3331
+ */
3332
+ async whep(sessionId, phoneId, offerSdp) {
3333
+ return this.client.textFromInit(
3334
+ `/api/sessions/${encodeURIComponent(sessionId)}/phones/${encodeURIComponent(phoneId)}/whep`,
3335
+ {
3336
+ method: "POST",
3337
+ body: offerSdp,
3338
+ headers: { "content-type": "application/sdp" }
3339
+ }
3340
+ );
3341
+ }
3342
+ /** 用户点「刷新画面」:让后台重新确认隧道服务并忘掉记住的 token。 */
3343
+ async wake(sessionId, phoneId) {
3344
+ await this.client.json(
3345
+ "POST",
3346
+ `/api/sessions/${encodeURIComponent(sessionId)}/phones/${encodeURIComponent(phoneId)}/wake`
3347
+ );
3348
+ }
3349
+ };
3350
+
3262
3351
  // src/resources/models.ts
3263
3352
  var ModelsResource = class {
3264
3353
  constructor(client) {
@@ -5021,6 +5110,7 @@ function createInitialSessionState(sessionId) {
5021
5110
  mode: null,
5022
5111
  connection: "disconnected",
5023
5112
  errorMessage: null,
5113
+ lastRun: null,
5024
5114
  askAnswers: {},
5025
5115
  agentLoops: {},
5026
5116
  activeCompaction: null,
@@ -6027,14 +6117,14 @@ var AgentSession = class _AgentSession {
6027
6117
  /** Prepare a durable text input without sending any network request. */
6028
6118
  prepareSend(content, options = {}) {
6029
6119
  if (!content.trim()) throw new Error("\u6D88\u606F\u4E0D\u80FD\u4E3A\u7A7A");
6030
- if (Object.keys(options).some((key) => !["mode", "model", "thinkingOverride", "reasoningLevel", "contextAttachments"].includes(key))) {
6120
+ if (Object.keys(options).some((key) => !["mode", "model", "thinkingOverride", "reasoningLevel", "contextAttachments", "queueWhenRunning", "headless"].includes(key))) {
6031
6121
  throw new Error("\u53EF\u9760\u6587\u672C\u53D1\u9001\u4E0D\u652F\u6301\u8FD9\u4E9B\u53C2\u6570\uFF0C\u8BF7\u4F7F\u7528 send()");
6032
6122
  }
6033
6123
  const snapshot2 = JSON.parse(JSON.stringify(options));
6034
6124
  if (snapshot2.contextAttachments) snapshot2.contextAttachments = snapshotContextAttachments(snapshot2.contextAttachments);
6035
6125
  return { sessionId: this.sessionId, requestId: createClientRequestId(), content, options: snapshot2 };
6036
6126
  }
6037
- /** Durably enqueue an ordinary text input. Retries must reuse the saved request. */
6127
+ /** Durably send a text input with its saved delivery intent. Retries must reuse the saved request. */
6038
6128
  async sendPrepared(request) {
6039
6129
  if (request.sessionId !== this.sessionId) throw new Error("\u53D1\u9001\u8BF7\u6C42\u4E0D\u5C5E\u4E8E\u5F53\u524D\u4F1A\u8BDD");
6040
6130
  try {
@@ -6046,6 +6136,8 @@ var AgentSession = class _AgentSession {
6046
6136
  prepared_options: {
6047
6137
  mode: request.options.mode,
6048
6138
  model: request.options.model,
6139
+ ...request.options.headless !== void 0 ? { headless: request.options.headless } : {},
6140
+ ...request.options.queueWhenRunning !== void 0 ? { queue_when_running: request.options.queueWhenRunning } : {},
6049
6141
  thinking_override: request.options.thinkingOverride,
6050
6142
  reasoning_level: request.options.reasoningLevel,
6051
6143
  ...request.options.contextAttachments ? { context_attachments: request.options.contextAttachments } : {}
@@ -6672,11 +6764,13 @@ var AgentSession = class _AgentSession {
6672
6764
  if (delivery.reload) {
6673
6765
  this.activeReplayPlaybackId = null;
6674
6766
  this.projectionBuilder.reset();
6767
+ this.update((state) => ({ ...state, lastRun: null }));
6675
6768
  this.pendingTurnPatches.clear();
6676
6769
  this.epochReloadRequested += 1;
6677
6770
  }
6678
6771
  this.runEpochReload();
6679
6772
  for (const event of events) {
6773
+ if (event.type === "chat:end") this.recordRunResult(event.payload);
6680
6774
  const playbackId = typeof event.payload.playback_id === "string" ? event.payload.playback_id : null;
6681
6775
  if (event.type === "chat:start") this.activeReplayPlaybackId = null;
6682
6776
  if (event.type === "replay:start" && playbackId) {
@@ -6776,6 +6870,14 @@ var AgentSession = class _AgentSession {
6776
6870
  isStreaming: true
6777
6871
  }));
6778
6872
  }
6873
+ recordRunResult(payload) {
6874
+ const runId = payload.chat_run_id;
6875
+ if (payload.queued || typeof runId !== "string" || !runId) return;
6876
+ this.update((state) => ({
6877
+ ...state,
6878
+ lastRun: { runId, status: payload.status ?? "completed", result: payload.result }
6879
+ }));
6880
+ }
6779
6881
  _handleChatEnd(payload = {}) {
6780
6882
  const status = payload.status ?? "completed";
6781
6883
  if (payload.queued === true) {
@@ -6785,6 +6887,7 @@ var AgentSession = class _AgentSession {
6785
6887
  return false;
6786
6888
  }
6787
6889
  }
6890
+ this.recordRunResult(payload);
6788
6891
  this.pendingChatRequestId = null;
6789
6892
  this.noteLiveStatus();
6790
6893
  this.flushPatches();
@@ -8008,6 +8111,8 @@ var BladeClient = class _BladeClient {
8008
8111
  auth;
8009
8112
  headless;
8010
8113
  computers;
8114
+ /** 用户接进来的手机:账号级清单、会话启用、画面信令。 */
8115
+ phones;
8011
8116
  models;
8012
8117
  sessions;
8013
8118
  /** 按用户读取的插件目录与图标;没有会话时也能用。 */
@@ -8033,6 +8138,7 @@ var BladeClient = class _BladeClient {
8033
8138
  this.auth = new AuthResource(this);
8034
8139
  this.headless = new HeadlessResource(this);
8035
8140
  this.computers = new ComputersResource(this);
8141
+ this.phones = new PhonesResource(this);
8036
8142
  this.models = new ModelsResource(this);
8037
8143
  this.sessions = new SessionsResource(this);
8038
8144
  this.plugins = new PluginsResource(this);
@@ -9072,6 +9178,9 @@ export {
9072
9178
  computerDaemonVersion,
9073
9179
  sortComputers,
9074
9180
  ComputersResource,
9181
+ phoneState,
9182
+ sortPhones,
9183
+ phoneSerialLabel,
9075
9184
  ModelsResource,
9076
9185
  isLocalConnectorName,
9077
9186
  ConnectorsResource,
@@ -9131,4 +9240,4 @@ export {
9131
9240
  BladeClient,
9132
9241
  BladeApplicationClient
9133
9242
  };
9134
- //# sourceMappingURL=chunk-XAXUPUD2.js.map
9243
+ //# sourceMappingURL=chunk-ME7O7RJC.js.map