openclaw-openagent 1.0.12 → 1.0.14

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.
Files changed (132) hide show
  1. package/dist/index.js +2 -0
  2. package/dist/src/config/config-schema.d.ts +9 -0
  3. package/dist/src/config/config-schema.js +6 -0
  4. package/dist/src/messaging/executor.d.ts +2 -3
  5. package/dist/src/messaging/executor.js +4 -31
  6. package/dist/src/plugin-ui/assets/openagent-override.js +124 -18
  7. package/dist/src/proxy/auth-proxy.js +5 -0
  8. package/dist/src/runtime/update-checker.d.ts +18 -0
  9. package/dist/src/runtime/update-checker.js +216 -0
  10. package/index.ts +3 -0
  11. package/package.json +1 -3
  12. package/src/app/channel-tools.ts +0 -249
  13. package/src/app/discovery-tools.ts +0 -273
  14. package/src/app/download-file-tool.ts +0 -133
  15. package/src/app/hooks.ts +0 -144
  16. package/src/app/index.ts +0 -84
  17. package/src/app/messaging-tools.ts +0 -79
  18. package/src/app/ops-tools.ts +0 -155
  19. package/src/app/remote-agent-tool.ts +0 -762
  20. package/src/app/types.ts +0 -68
  21. package/src/app/upload-file-tool.ts +0 -411
  22. package/src/app/verbose-preflight.ts +0 -190
  23. package/src/auth/config.ts +0 -266
  24. package/src/auth/credential-manager.ts +0 -146
  25. package/src/auth/index.ts +0 -24
  26. package/src/auth/verify.ts +0 -99
  27. package/src/channel.ts +0 -566
  28. package/src/compat.ts +0 -94
  29. package/src/config/config-schema.ts +0 -39
  30. package/src/messaging/aggregator.ts +0 -120
  31. package/src/messaging/collector.ts +0 -89
  32. package/src/messaging/executor.ts +0 -72
  33. package/src/messaging/inbound.ts +0 -150
  34. package/src/messaging/index.ts +0 -11
  35. package/src/messaging/mention-protocol.ts +0 -94
  36. package/src/messaging/process-c2c-request.ts +0 -564
  37. package/src/messaging/process-message.ts +0 -373
  38. package/src/messaging/scheduler.ts +0 -55
  39. package/src/messaging/types.ts +0 -38
  40. package/src/plugin-ui/adapters/oc-2026-04.js +0 -103
  41. package/src/plugin-ui/adapters/oc-2026-05.js +0 -125
  42. package/src/plugin-ui/adapters/oc-2026-06.js +0 -125
  43. package/src/plugin-ui/adapters/oc-unknown.js +0 -48
  44. package/src/plugin-ui/assets/agentbook-icon.svg +0 -5
  45. package/src/plugin-ui/assets/bg.png +0 -0
  46. package/src/plugin-ui/assets/icon.png +0 -0
  47. package/src/plugin-ui/assets/magic.svg +0 -5
  48. package/src/plugin-ui/assets/openagent-override.js +0 -11156
  49. package/src/plugin-ui/build.cjs +0 -463
  50. package/src/plugin-ui/index.ts +0 -18
  51. package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -570
  52. package/src/plugin-ui/modules/agent-book/panel/agent-card.js +0 -170
  53. package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -643
  54. package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +0 -665
  55. package/src/plugin-ui/modules/agent-book/panel/mention-state.js +0 -206
  56. package/src/plugin-ui/modules/agent-book/panel/styles.js +0 -833
  57. package/src/plugin-ui/modules/agent-book/panel/theme-adapter.js +0 -86
  58. package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +0 -295
  59. package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +0 -211
  60. package/src/plugin-ui/modules/agent-book/scanner.js +0 -131
  61. package/src/plugin-ui/modules/agent-book/travelcard/travel-cards.js +0 -500
  62. package/src/plugin-ui/modules/agent-book/travelcard/travel-engine.js +0 -652
  63. package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +0 -256
  64. package/src/plugin-ui/modules/loader/bootstrap.js +0 -38
  65. package/src/plugin-ui/modules/loader/shared-state.js +0 -838
  66. package/src/plugin-ui/modules/loader/ws-intercept.js +0 -199
  67. package/src/plugin-ui/modules/remote-agent/chunk-parser.js +0 -161
  68. package/src/plugin-ui/modules/remote-agent/execution-card.js +0 -530
  69. package/src/plugin-ui/modules/remote-agent/markdown-renderer.js +0 -256
  70. package/src/plugin-ui/modules/remote-agent/media-links.js +0 -151
  71. package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +0 -134
  72. package/src/plugin-ui/modules/remote-agent/output-card.js +0 -342
  73. package/src/plugin-ui/modules/remote-agent/progress-store.js +0 -363
  74. package/src/plugin-ui/modules/remote-agent/render-hooks.js +0 -1700
  75. package/src/plugin-ui/modules/remote-agent/styles.js +0 -896
  76. package/src/plugin-ui/modules/remote-agent/tool-card-model.js +0 -130
  77. package/src/plugin-ui/postinstall-deploy.cjs +0 -52
  78. package/src/plugin-ui/ui-extension-loader/backup.ts +0 -92
  79. package/src/plugin-ui/ui-extension-loader/index.ts +0 -276
  80. package/src/plugin-ui/ui-extension-loader/locator.ts +0 -152
  81. package/src/plugin-ui/ui-extension-loader/manifest.ts +0 -57
  82. package/src/plugin-ui/ui-extension-loader/registry-regex.ts +0 -944
  83. package/src/plugin-ui/ui-extension-loader/removed-extensions.ts +0 -70
  84. package/src/plugin-ui/ui-extension-loader/types.ts +0 -72
  85. package/src/proxy/auth-proxy.ts +0 -409
  86. package/src/runtime/account.ts +0 -549
  87. package/src/runtime/index.ts +0 -7
  88. package/src/runtime/plugin-runtime.ts +0 -94
  89. package/src/runtime/registry.ts +0 -71
  90. package/src/sdk/CLASS_MAP.md +0 -143
  91. package/src/sdk/index.d.ts +0 -126
  92. package/src/sdk/index.js +0 -23990
  93. package/src/sdk/modules/cloud-search-module.js +0 -1117
  94. package/src/sdk/modules/follow-module.js +0 -1069
  95. package/src/sdk/modules/group-module.js +0 -7397
  96. package/src/sdk/modules/relationship-module.js +0 -2269
  97. package/src/sdk/modules/signaling-module.js +0 -1468
  98. package/src/sdk/modules/tim-upload-plugin.js +0 -730
  99. package/src/sdk/node-env/http-request.js +0 -90
  100. package/src/sdk/node-env/index.js +0 -57
  101. package/src/sdk/node-env/storage.js +0 -114
  102. package/src/sdk/package.json +0 -10
  103. package/src/sdk/tsconfig.json +0 -16
  104. package/src/state/pending-invocation-store.ts +0 -43
  105. package/src/state/store.ts +0 -756
  106. package/src/tim/c2c.ts +0 -451
  107. package/src/tim/channels.ts +0 -364
  108. package/src/tim/client.ts +0 -330
  109. package/src/tim/index.ts +0 -18
  110. package/src/tim/messages.ts +0 -166
  111. package/src/tim/sdk-logger-init.ts +0 -50
  112. package/src/tools.ts +0 -10
  113. package/src/transport/factory.ts +0 -95
  114. package/src/transport/oasn/index.ts +0 -17
  115. package/src/transport/oasn/oasn-agent-card.ts +0 -111
  116. package/src/transport/oasn/oasn-discovery.ts +0 -108
  117. package/src/transport/oasn/oasn-files.ts +0 -210
  118. package/src/transport/oasn/oasn-http.ts +0 -529
  119. package/src/transport/oasn/oasn-invocation.ts +0 -604
  120. package/src/transport/oasn/oasn-normalize.ts +0 -133
  121. package/src/transport/oasn/oasn-register.ts +0 -33
  122. package/src/transport/oasn/oasn-transport.ts +0 -318
  123. package/src/transport/oasn/oasn-types.ts +0 -404
  124. package/src/transport/tim/index.ts +0 -8
  125. package/src/transport/tim/tim-transport.ts +0 -515
  126. package/src/transport/types.ts +0 -538
  127. package/src/types/openclaw-plugin-sdk-media-store.d.ts +0 -9
  128. package/src/types/openclaw.d.ts +0 -97
  129. package/src/types/tencentcloud-chat.d.ts +0 -15
  130. package/src/util/http.ts +0 -113
  131. package/src/util/logger.ts +0 -131
  132. package/src/util/url-resolver.ts +0 -77
@@ -1,604 +0,0 @@
1
- /**
2
- * OASN Invocation —— 任务调用 + 轮询订阅 ⭐
3
- *
4
- * 设计依据:
5
- * - docs/specs/2026-06-16-oasn-api-module-mapping.md §2.14 / §2.15
6
- * - docs/specs/2026-06-16-oasn-transport-abstraction-design.md
7
- * §11.1(idempotency_key 持久化)
8
- * §11.2(abort 与 cancel 缺失)
9
- * §11.3(timeoutMs ↔ effective_timeout_ms 协调)
10
- * §11.8(subscribeToInvocation 抽象 + SSE 升级)
11
- *
12
- * 子模块包含 3 个核心能力:
13
- *
14
- * 1. createInvocation() POST /api/invocations
15
- * - 维护 idempotency_key(接受外部传入,便于 §11.1 持久化复用)
16
- * - 返回 invocation_id / session_id / effective_timeout_ms / next_poll_after_ms
17
- *
18
- * 2. subscribeToInvocation(id, opts)
19
- * - 默认实现 = 轮询循环
20
- * - 上层 waitForTaskResult 只调用此抽象,对推送/轮询无感知
21
- * - 后续可在此处理 SSE 升级(探测 Accept: text/event-stream),保持外部 API 不变
22
- *
23
- * 3. waitForTaskResult(handle, opts)
24
- * - 包装 subscribeToInvocation,转换为 TaskResult
25
- * - 终态时拉取 artifacts 列表(不下载内容,仅引用)
26
- * - 协调本地 timeoutMs 与服务端 effective_timeout_ms(§11.3)
27
- * - signal.aborted → 标记 abandoned(§11.2,不调用 cancel API 因为不存在)
28
- */
29
-
30
- import { randomUUID } from 'node:crypto';
31
-
32
- import { logger } from '../../util/logger.js';
33
- import {
34
- makeTransportError,
35
- type ArtifactRef,
36
- type ProgressCallback,
37
- type TaskHandle,
38
- type TaskRequest,
39
- type TaskResult,
40
- type TransportError,
41
- type WebuiContinuationRef,
42
- } from '../types.js';
43
- import type { OasnHttpClient } from './oasn-http.js';
44
- import {
45
- isInvocationTerminal,
46
- OASN_ENDPOINTS,
47
- type ArtifactResponse,
48
- type CreateInvocationRequest,
49
- type CreateInvocationResponse,
50
- type GetInvocationResponse,
51
- type InvocationStatus,
52
- type VisibleResultResponse,
53
- } from './oasn-types.js';
54
-
55
- /** Invocation 持久化钩子(接 StateStore.pending_invocations,§11.1) */
56
- export interface PendingInvocationStore {
57
- /** sendTask 前查询:若同一 toolCallId 已有未完成 invocation,返回已存的 idempotency_key */
58
- getPending(toolCallId: string): { idempotency_key: string; invocation_id?: string } | null;
59
- /** sendTask 拿到 invocation_id 后回写 */
60
- upsertPending(record: {
61
- tool_call_id: string;
62
- idempotency_key: string;
63
- invocation_id?: string;
64
- session_id?: string;
65
- target_agent_id?: string;
66
- status: string;
67
- }): void;
68
- /** 终态时清理 */
69
- markTerminal(invocationId: string, status: string): void;
70
- /** §11.2 abort 时标记为 abandoned */
71
- markAbandoned(invocationId: string): void;
72
- }
73
-
74
- /**
75
- * subscribeToInvocation 选项。
76
- * 抽象层屏蔽轮询/SSE 差异:上层只关心 onProgress / onComplete / signal。
77
- */
78
- export interface SubscribeOptions {
79
- /** 进度推送回调 */
80
- onProgress?: (progress: { content: string; raw: GetInvocationResponse['progress'] }) => void;
81
- /** 终态回调(succeeded/failed/timed_out/cancelled) */
82
- onComplete: (final: GetInvocationResponse) => void;
83
- /** abort 信号 */
84
- signal?: AbortSignal;
85
- /** 总等待时长上限(ms)—— 由 waitForTaskResult 协调后传入 */
86
- totalTimeoutMs: number;
87
- /** 服务端首轮建议间隔(来自 createInvocation 响应) */
88
- initialPollAfterMs?: number;
89
- }
90
-
91
- /** sendTask 拓展输入(OASN 特有) */
92
- export interface CreateInvocationOptions {
93
- /**
94
- * §11.1:客户端持久化的工具调用 id。提供后会与 PendingInvocationStore 联动:
95
- * - 若已存在未完成记录 → 复用其 idempotency_key(实现进程崩溃恢复)
96
- * - 否则生成新 UUID 并落盘
97
- *
98
- * 不传时退化为每次随机 UUID(无持久化)。
99
- */
100
- toolCallId?: string;
101
- /** 期望超时(毫秒),服务端可能裁决(effective_timeout_ms) */
102
- timeoutMs?: number;
103
- /** 客户端分组标识,便于服务端聚合排障 */
104
- clientRequestGroupId?: string;
105
- }
106
-
107
- /** 轮询常量 —— 服务端 next_poll_after_ms 的下限保护,避免风暴 */
108
- const MIN_POLL_INTERVAL_MS = 500;
109
- /** 轮询间隔上限(防止服务端返回异常大值) */
110
- const MAX_POLL_INTERVAL_MS = 30_000;
111
- /** 当 next_poll_after_ms 缺失时使用的兜底值 */
112
- const FALLBACK_POLL_INTERVAL_MS = 2_000;
113
-
114
- export class OasnInvocation {
115
- /** invocation_id → effective_timeout_ms(waitForTaskResult §11.3 用) */
116
- private _effectiveTimeoutMap = new Map<string, number>();
117
- /** invocation_id → 首轮 next_poll_after_ms */
118
- private _initialPollMap = new Map<string, number>();
119
-
120
- constructor(
121
- private readonly _http: OasnHttpClient,
122
- private readonly _pendingStore?: PendingInvocationStore,
123
- ) {}
124
-
125
- // ═══════════════════════════════════════════════════════════════
126
- // sendTask(POST /api/invocations)
127
- // ═══════════════════════════════════════════════════════════════
128
-
129
- /**
130
- * 创建 Invocation。
131
- *
132
- * 返回 TaskHandle —— 含 invocation_id / session_id / effective_timeout_ms / next_poll_after_ms。
133
- * 上层将此 handle 传给 waitForTaskResult。
134
- */
135
- async sendTask(
136
- targetAgentId: string,
137
- task: TaskRequest,
138
- options?: CreateInvocationOptions,
139
- ): Promise<TaskHandle> {
140
- // §11.1:尝试复用 pending 记录
141
- let idempotencyKey: string;
142
- let pendingRecord: ReturnType<NonNullable<typeof this._pendingStore>['getPending']> | null = null;
143
- if (options?.toolCallId && this._pendingStore) {
144
- pendingRecord = this._pendingStore.getPending(options.toolCallId);
145
- }
146
- if (pendingRecord) {
147
- idempotencyKey = pendingRecord.idempotency_key;
148
- logger.info(
149
- `[transport/oasn/invocation] reuse pending tool_call_id=${options?.toolCallId} `
150
- + `idempotency_key=${idempotencyKey}`,
151
- );
152
- } else {
153
- idempotencyKey = randomUUID();
154
- }
155
-
156
- const body: CreateInvocationRequest = {
157
- idempotency_key: idempotencyKey,
158
- service_agent_id: targetAgentId,
159
- input: { task: task.task },
160
- };
161
- if (task.sessionId) body.session_id = task.sessionId;
162
- if (task.inputFileRefs?.length) body.input_file_refs = task.inputFileRefs;
163
- if (task.inputArtifactRefs?.length) body.input_artifact_refs = task.inputArtifactRefs;
164
- if (options?.timeoutMs !== undefined) body.timeout_ms = options.timeoutMs;
165
- if (options?.clientRequestGroupId) body.client_request_group_id = options.clientRequestGroupId;
166
-
167
- logger.info(
168
- `[transport/oasn/invocation] sendTask target=${targetAgentId} `
169
- + `idempotency_key=${idempotencyKey} replay=${pendingRecord ? 'maybe' : 'new'}`,
170
- );
171
-
172
- // 注意:服务端按 idempotency_key 去重,重试是安全的;此处保留默认重试策略
173
- const resp = await this._http.post<CreateInvocationResponse>(
174
- OASN_ENDPOINTS.INVOCATIONS,
175
- body,
176
- );
177
- const created = this._normalizeCreateInvocation(resp, options?.timeoutMs);
178
- if (!created.invocationId) {
179
- throw makeTransportError('INVALID_INVOCATION_RESPONSE', 'Create invocation response missing invocationId');
180
- }
181
-
182
- logger.info(
183
- `[transport/oasn/invocation] created invocation_id=${created.invocationId} `
184
- + `status=${created.status} effective_timeout_ms=${created.effectiveTimeoutMs} `
185
- + `replay=${created.idempotentReplay}`,
186
- );
187
-
188
- // 回填 pending 记录
189
- if (options?.toolCallId && this._pendingStore) {
190
- this._pendingStore.upsertPending({
191
- tool_call_id: options.toolCallId,
192
- idempotency_key: idempotencyKey,
193
- invocation_id: created.invocationId,
194
- session_id: created.sessionId,
195
- target_agent_id: targetAgentId,
196
- status: created.status,
197
- });
198
- }
199
-
200
- // 缓存服务端裁决参数,留给 waitForTaskResult 使用
201
- this._effectiveTimeoutMap.set(created.invocationId, created.effectiveTimeoutMs);
202
- this._initialPollMap.set(created.invocationId, created.nextPollAfterMs);
203
-
204
- return {
205
- invocationId: created.invocationId,
206
- sessionId: created.sessionId,
207
- effectiveTimeoutMs: created.effectiveTimeoutMs,
208
- nextPollAfterMs: created.nextPollAfterMs,
209
- };
210
- }
211
-
212
- // ═══════════════════════════════════════════════════════════════
213
- // subscribeToInvocation —— §11.8 抽象层
214
- // ═══════════════════════════════════════════════════════════════
215
-
216
- /**
217
- * 订阅 Invocation 状态变化,直至终态或 abort/timeout。
218
- *
219
- * 当前实现:纯轮询循环。
220
- * 后续若服务端支持 SSE,可在此函数内部探测升级,外部 API 保持不变。
221
- *
222
- * 返回 Promise,在终态/超时/abort 时 resolve(内部不抛错,错误由 onComplete 携带 final.status 判定)。
223
- */
224
- async subscribeToInvocation(
225
- invocationId: string,
226
- options: SubscribeOptions,
227
- ): Promise<void> {
228
- const { onProgress, onComplete, signal, totalTimeoutMs, initialPollAfterMs } = options;
229
- const startAt = Date.now();
230
- let nextDelay = this._clampPollInterval(initialPollAfterMs ?? this._initialPollMap.get(invocationId));
231
-
232
- while (true) {
233
- // ── abort 检查 ────────────────────────────────────────────
234
- if (signal?.aborted) {
235
- logger.warn(`[transport/oasn/invocation] subscribe aborted invocation_id=${invocationId}`);
236
- // §11.2:服务端没有 cancel API,只能本地标记
237
- this._pendingStore?.markAbandoned(invocationId);
238
- // 用一个虚拟的"cancelled"终态通知上层
239
- onComplete(this._buildAbandonedResponse(invocationId));
240
- return;
241
- }
242
-
243
- // ── 总超时检查 ────────────────────────────────────────────
244
- const elapsed = Date.now() - startAt;
245
- if (elapsed >= totalTimeoutMs) {
246
- logger.warn(
247
- `[transport/oasn/invocation] subscribe timed out invocation_id=${invocationId} `
248
- + `elapsed=${elapsed}ms limit=${totalTimeoutMs}ms`,
249
- );
250
- this._pendingStore?.markAbandoned(invocationId);
251
- onComplete(this._buildTimeoutResponse(invocationId));
252
- return;
253
- }
254
-
255
- // ── 等待下次轮询 ──────────────────────────────────────────
256
- const remaining = totalTimeoutMs - elapsed;
257
- const waitMs = Math.min(nextDelay, remaining);
258
- await this._sleepWithAbort(waitMs, signal);
259
- if (signal?.aborted) continue; // 让下一轮迭代命中上面 abort 分支统一处理
260
-
261
- // ── 拉取最新状态 ──────────────────────────────────────────
262
- let resp: GetInvocationResponse;
263
- try {
264
- resp = await this._http.get<GetInvocationResponse>(
265
- OASN_ENDPOINTS.INVOCATION(invocationId),
266
- );
267
- } catch (err) {
268
- const te = err as TransportError;
269
- // 404 当成 cancelled —— 服务端可能已清理
270
- if (te.code === 'INVOCATION_NOT_FOUND' || te.code === 'HTTP_404') {
271
- logger.warn(`[transport/oasn/invocation] invocation gone: ${invocationId}`);
272
- onComplete(this._build404Response(invocationId));
273
- return;
274
- }
275
- // 其它错误:retryable 的等下一轮,否则向上抛
276
- if (te.retryable) {
277
- logger.warn(
278
- `[transport/oasn/invocation] poll retryable error code=${te.code} for ${invocationId}, will retry`,
279
- );
280
- // 延后下一轮(指数式,但不超过上限)
281
- nextDelay = this._clampPollInterval(nextDelay * 2);
282
- continue;
283
- }
284
- throw err;
285
- }
286
-
287
- // ── 进度推送 ───────────────────────────────────────────────
288
- const safeStatusMessage = resp.progress?.safeStatusMessage ?? resp.progress?.safe_status_message;
289
- if (onProgress && safeStatusMessage) {
290
- try {
291
- onProgress({
292
- content: safeStatusMessage,
293
- raw: resp.progress,
294
- });
295
- } catch (cbErr) {
296
- // 不让回调异常影响轮询
297
- logger.warn(`[transport/oasn/invocation] onProgress callback threw: ${(cbErr as Error).message}`);
298
- }
299
- }
300
-
301
- // ── 终态判定 ───────────────────────────────────────────────
302
- if (isInvocationTerminal(resp.status)) {
303
- logger.info(`[transport/oasn/invocation] terminal status=${resp.status} invocation_id=${invocationId}`);
304
- this._pendingStore?.markTerminal(invocationId, resp.status);
305
- onComplete(resp);
306
- return;
307
- }
308
-
309
- // ── 决定下次轮询间隔 ──────────────────────────────────────
310
- nextDelay = this._clampPollInterval(resp.nextPollAfterMs ?? resp.next_poll_after_ms ?? FALLBACK_POLL_INTERVAL_MS);
311
- }
312
- }
313
-
314
- // ═══════════════════════════════════════════════════════════════
315
- // waitForTaskResult —— 包装 subscribeToInvocation 为 TaskResult
316
- // ═══════════════════════════════════════════════════════════════
317
-
318
- /**
319
- * 等待任务结果(终态后转换为 TaskResult)。
320
- *
321
- * §11.3 协调:
322
- * 实际等待时间 = max(本地 timeoutMs, 服务端 effective_timeout_ms)
323
- * 差距 > 30% 时输出 warn,提示用户调整。
324
- */
325
- async waitForTaskResult(
326
- handle: TaskHandle,
327
- options: { timeoutMs: number; signal?: AbortSignal; onProgress?: ProgressCallback },
328
- ): Promise<TaskResult> {
329
- const effective = handle.effectiveTimeoutMs
330
- ?? this._effectiveTimeoutMap.get(handle.invocationId)
331
- ?? options.timeoutMs;
332
-
333
- const totalTimeoutMs = Math.max(options.timeoutMs, effective);
334
-
335
- // §11.3:差距超过 30% 时提示
336
- if (effective > options.timeoutMs * 1.3) {
337
- logger.warn(
338
- `[transport/oasn/invocation] effective_timeout_ms=${effective} exceeds local timeoutMs=${options.timeoutMs} `
339
- + `by >30% (invocation_id=${handle.invocationId}); consider increasing local timeout`,
340
- );
341
- } else if (options.timeoutMs > effective * 1.3) {
342
- logger.warn(
343
- `[transport/oasn/invocation] local timeoutMs=${options.timeoutMs} exceeds effective_timeout_ms=${effective} `
344
- + `by >30% (invocation_id=${handle.invocationId}); server may give up earlier`,
345
- );
346
- }
347
-
348
- return new Promise<TaskResult>((resolve, reject) => {
349
- this.subscribeToInvocation(handle.invocationId, {
350
- totalTimeoutMs,
351
- signal: options.signal,
352
- initialPollAfterMs: handle.nextPollAfterMs,
353
- onProgress: options.onProgress
354
- ? (p) => options.onProgress!({
355
- requestId: handle.invocationId,
356
- content: p.content,
357
- })
358
- : undefined,
359
- onComplete: (final) => {
360
- try {
361
- const result = this._toTaskResult(handle.invocationId, final);
362
- // 终态错误转换为 reject,便于上层 catch
363
- if (result.status === 'error') {
364
- reject(this._terminalError(final, handle));
365
- return;
366
- }
367
- resolve(result);
368
- } catch (err) {
369
- reject(err);
370
- }
371
- },
372
- }).catch(reject);
373
- });
374
- }
375
-
376
- // ═══════════════════════════════════════════════════════════════
377
- // 内部工具
378
- // ═══════════════════════════════════════════════════════════════
379
-
380
- /**
381
- * 把终态 Invocation 响应转换为 TaskResult。
382
- *
383
- * - succeeded → status:'complete' + content=summary + artifacts + webuiContinuation
384
- * - 其它终态 → status:'error' + content=user_error.message
385
- *
386
- * content 的归一路径:
387
- * inlineResult.summary > visible_result.inline_result_summary > ''
388
- *
389
- * webui_continuation 仅在 succeeded 时抽取(失败场景后端不保证存在)。
390
- */
391
- private _toTaskResult(invocationId: string, final: GetInvocationResponse): TaskResult {
392
- const webuiContinuation = this._toWebuiContinuation(final);
393
-
394
- if (final.status === 'succeeded') {
395
- const inlineResult = final.inlineResult ?? final.inline_result;
396
- const visible = final.visibleResult ?? final.visible_result;
397
- const summary = inlineResult?.summary
398
- ?? visible?.inlineResultSummary
399
- ?? visible?.inline_result_summary
400
- ?? '';
401
- return {
402
- requestId: invocationId,
403
- status: 'complete',
404
- content: summary,
405
- artifacts: (final.artifacts ?? []).map(this._toArtifactRef),
406
- webuiContinuation,
407
- };
408
- }
409
-
410
- const message = this._invocationFailureMessage(
411
- final,
412
- `Invocation ended with status=${final.status}`,
413
- );
414
- return {
415
- requestId: invocationId,
416
- status: 'error',
417
- content: message,
418
- artifacts: (final.artifacts ?? []).map(this._toArtifactRef),
419
- webuiContinuation,
420
- };
421
- }
422
-
423
- /**
424
- * 从终态响应里抽取 WebUI continuation(camelCase / snake_case 双归一)。
425
- * 缺必要字段(id / launch_url)时返回 undefined,调用方按 falsy 处理即可。
426
- */
427
- private _toWebuiContinuation(final: GetInvocationResponse): WebuiContinuationRef | undefined {
428
- const visible: VisibleResultResponse | undefined =
429
- final.visibleResult ?? final.visible_result;
430
- const raw = visible?.webuiContinuation ?? visible?.webui_continuation;
431
- if (!raw) return undefined;
432
-
433
- const id = raw.continuationId ?? raw.continuation_id;
434
- const launchUrl = raw.launchUrl ?? raw.launch_url;
435
- if (!id || !launchUrl) return undefined;
436
-
437
- return {
438
- id,
439
- launchUrl,
440
- displayName: raw.displayName ?? raw.display_name ?? 'Open WebUI',
441
- visibleState: raw.visibleState ?? raw.visible_state ?? 'available',
442
- sessionId: raw.sessionId ?? raw.session_id,
443
- };
444
- }
445
-
446
- /** 终态错误专用 TransportError,便于上层 catch */
447
- private _terminalError(final: GetInvocationResponse, handle: TaskHandle): TransportError {
448
- const code = this._invocationFailureCode(final) ?? `INVOCATION_${final.status.toUpperCase()}`;
449
- const msg = this._invocationFailureMessage(final, `Invocation ${final.status}`);
450
- return makeTransportError(code, msg, {
451
- invocationId: handle.invocationId,
452
- sessionId: handle.sessionId,
453
- });
454
- }
455
-
456
- private _invocationFailureCode(final: GetInvocationResponse): string | undefined {
457
- return this._firstText(
458
- final.userError?.code,
459
- final.user_error?.code,
460
- final.userErrorCode,
461
- final.user_error_code,
462
- );
463
- }
464
-
465
- private _invocationFailureMessage(final: GetInvocationResponse, fallback: string): string {
466
- const userMessage = this._firstText(
467
- final.userError?.message,
468
- final.user_error?.message,
469
- final.userErrorMessage,
470
- final.user_error_message,
471
- );
472
- if (userMessage) return userMessage;
473
-
474
- const inlineResult = final.inlineResult ?? final.inline_result;
475
- const visible = final.visibleResult ?? final.visible_result;
476
- const fallbackMessage = this._firstText(
477
- inlineResult?.summary,
478
- visible?.inlineResultSummary,
479
- visible?.inline_result_summary,
480
- final.progress?.safeStatusMessage,
481
- final.progress?.safe_status_message,
482
- );
483
- const code = this._invocationFailureCode(final);
484
- if (fallbackMessage && code && !fallbackMessage.includes(code)) return `${fallbackMessage} (${code})`;
485
- return fallbackMessage ?? code ?? fallback;
486
- }
487
-
488
- private _firstText(...values: Array<unknown>): string | undefined {
489
- for (const value of values) {
490
- if (typeof value !== 'string') continue;
491
- const trimmed = value.trim();
492
- if (trimmed) return trimmed;
493
- }
494
- return undefined;
495
- }
496
-
497
- private _toArtifactRef(art: ArtifactResponse): ArtifactRef {
498
- return {
499
- id: art.artifactId ?? art.artifact_id ?? '',
500
- displayName: art.displayName ?? art.display_name ?? 'artifact',
501
- mimeType: art.mimeType ?? art.mime_type ?? 'application/octet-stream',
502
- sizeBytes: art.sizeBytes ?? art.size_bytes ?? 0,
503
- contentAvailable: art.contentAvailable ?? art.content_available ?? false,
504
- contentUrl: art.contentUrl ?? art.content_url ?? '',
505
- };
506
- }
507
-
508
- private _normalizeCreateInvocation(
509
- resp: CreateInvocationResponse,
510
- requestedTimeoutMs: number | undefined,
511
- ): {
512
- invocationId: string;
513
- sessionId: string | undefined;
514
- status: CreateInvocationResponse['status'];
515
- effectiveTimeoutMs: number;
516
- nextPollAfterMs: number;
517
- idempotentReplay: boolean;
518
- } {
519
- return {
520
- invocationId: resp.invocationId ?? resp.invocation_id ?? '',
521
- sessionId: resp.sessionId ?? resp.session_id,
522
- status: resp.status,
523
- effectiveTimeoutMs: resp.effectiveTimeoutMs ?? resp.effective_timeout_ms ?? requestedTimeoutMs ?? 0,
524
- nextPollAfterMs: resp.nextPollAfterMs ?? resp.next_poll_after_ms ?? FALLBACK_POLL_INTERVAL_MS,
525
- idempotentReplay: resp.idempotentReplay ?? resp.idempotent_replay ?? false,
526
- };
527
- }
528
-
529
- /** clamp 轮询间隔到合理范围 */
530
- private _clampPollInterval(ms: number | null | undefined): number {
531
- if (!ms || ms <= 0) return FALLBACK_POLL_INTERVAL_MS;
532
- return Math.min(MAX_POLL_INTERVAL_MS, Math.max(MIN_POLL_INTERVAL_MS, ms));
533
- }
534
-
535
- /** 可被 abort 中断的 sleep */
536
- private _sleepWithAbort(ms: number, signal?: AbortSignal): Promise<void> {
537
- return new Promise((resolve) => {
538
- if (signal?.aborted) {
539
- resolve();
540
- return;
541
- }
542
- const t = setTimeout(() => {
543
- if (signal) signal.removeEventListener('abort', onAbort);
544
- resolve();
545
- }, ms);
546
- const onAbort = () => {
547
- clearTimeout(t);
548
- resolve();
549
- };
550
- signal?.addEventListener('abort', onAbort, { once: true });
551
- });
552
- }
553
-
554
- /** abort 场景虚拟终态 —— 不调用服务端 cancel(API 不存在 §11.2) */
555
- private _buildAbandonedResponse(invocationId: string): GetInvocationResponse {
556
- const now = new Date().toISOString();
557
- return {
558
- invocation_id: invocationId,
559
- session_id: '',
560
- status: 'cancelled' as InvocationStatus,
561
- next_poll_after_ms: null,
562
- created_at: now,
563
- updated_at: now,
564
- user_error: {
565
- code: 'CLIENT_ABORTED',
566
- message: '已发起调用但放弃跟踪结果(服务端可能仍在执行)',
567
- },
568
- };
569
- }
570
-
571
- /** timeout 虚拟终态 */
572
- private _buildTimeoutResponse(invocationId: string): GetInvocationResponse {
573
- const now = new Date().toISOString();
574
- return {
575
- invocation_id: invocationId,
576
- session_id: '',
577
- status: 'timed_out' as InvocationStatus,
578
- next_poll_after_ms: null,
579
- created_at: now,
580
- updated_at: now,
581
- user_error: {
582
- code: 'CLIENT_TIMEOUT',
583
- message: '本地等待超时,服务端可能仍在执行',
584
- },
585
- };
586
- }
587
-
588
- /** invocation_id 不存在时的虚拟终态 */
589
- private _build404Response(invocationId: string): GetInvocationResponse {
590
- const now = new Date().toISOString();
591
- return {
592
- invocation_id: invocationId,
593
- session_id: '',
594
- status: 'cancelled' as InvocationStatus,
595
- next_poll_after_ms: null,
596
- created_at: now,
597
- updated_at: now,
598
- user_error: {
599
- code: 'INVOCATION_NOT_FOUND',
600
- message: 'Invocation 不存在或已清理',
601
- },
602
- };
603
- }
604
- }