openclaw-openagent 1.0.13 → 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.
- package/dist/src/messaging/executor.d.ts +2 -3
- package/dist/src/messaging/executor.js +4 -31
- package/dist/src/plugin-ui/assets/openagent-override.js +4 -5
- package/dist/src/runtime/update-checker.d.ts +1 -1
- package/dist/src/runtime/update-checker.js +9 -46
- package/package.json +1 -3
- package/src/app/channel-tools.ts +0 -249
- package/src/app/discovery-tools.ts +0 -273
- package/src/app/download-file-tool.ts +0 -133
- package/src/app/hooks.ts +0 -144
- package/src/app/index.ts +0 -84
- package/src/app/messaging-tools.ts +0 -79
- package/src/app/ops-tools.ts +0 -155
- package/src/app/remote-agent-tool.ts +0 -762
- package/src/app/types.ts +0 -68
- package/src/app/upload-file-tool.ts +0 -411
- package/src/app/verbose-preflight.ts +0 -190
- package/src/auth/config.ts +0 -266
- package/src/auth/credential-manager.ts +0 -146
- package/src/auth/index.ts +0 -24
- package/src/auth/verify.ts +0 -99
- package/src/channel.ts +0 -566
- package/src/compat.ts +0 -94
- package/src/config/config-schema.ts +0 -45
- package/src/messaging/aggregator.ts +0 -120
- package/src/messaging/collector.ts +0 -89
- package/src/messaging/executor.ts +0 -72
- package/src/messaging/inbound.ts +0 -150
- package/src/messaging/index.ts +0 -11
- package/src/messaging/mention-protocol.ts +0 -94
- package/src/messaging/process-c2c-request.ts +0 -564
- package/src/messaging/process-message.ts +0 -373
- package/src/messaging/scheduler.ts +0 -55
- package/src/messaging/types.ts +0 -38
- package/src/plugin-ui/adapters/oc-2026-04.js +0 -103
- package/src/plugin-ui/adapters/oc-2026-05.js +0 -125
- package/src/plugin-ui/adapters/oc-2026-06.js +0 -125
- package/src/plugin-ui/adapters/oc-unknown.js +0 -48
- package/src/plugin-ui/assets/agentbook-icon.svg +0 -5
- package/src/plugin-ui/assets/bg.png +0 -0
- package/src/plugin-ui/assets/icon.png +0 -0
- package/src/plugin-ui/assets/magic.svg +0 -5
- package/src/plugin-ui/assets/openagent-override.js +0 -11263
- package/src/plugin-ui/build.cjs +0 -463
- package/src/plugin-ui/index.ts +0 -18
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -570
- package/src/plugin-ui/modules/agent-book/panel/agent-card.js +0 -170
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -643
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +0 -665
- package/src/plugin-ui/modules/agent-book/panel/mention-state.js +0 -206
- package/src/plugin-ui/modules/agent-book/panel/styles.js +0 -832
- package/src/plugin-ui/modules/agent-book/panel/theme-adapter.js +0 -86
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +0 -295
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +0 -211
- package/src/plugin-ui/modules/agent-book/scanner.js +0 -131
- package/src/plugin-ui/modules/agent-book/travelcard/travel-cards.js +0 -500
- package/src/plugin-ui/modules/agent-book/travelcard/travel-engine.js +0 -652
- package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +0 -262
- package/src/plugin-ui/modules/loader/bootstrap.js +0 -133
- package/src/plugin-ui/modules/loader/shared-state.js +0 -838
- package/src/plugin-ui/modules/loader/ws-intercept.js +0 -199
- package/src/plugin-ui/modules/remote-agent/chunk-parser.js +0 -161
- package/src/plugin-ui/modules/remote-agent/execution-card.js +0 -530
- package/src/plugin-ui/modules/remote-agent/markdown-renderer.js +0 -256
- package/src/plugin-ui/modules/remote-agent/media-links.js +0 -151
- package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +0 -134
- package/src/plugin-ui/modules/remote-agent/output-card.js +0 -342
- package/src/plugin-ui/modules/remote-agent/progress-store.js +0 -363
- package/src/plugin-ui/modules/remote-agent/render-hooks.js +0 -1700
- package/src/plugin-ui/modules/remote-agent/styles.js +0 -903
- package/src/plugin-ui/modules/remote-agent/tool-card-model.js +0 -130
- package/src/plugin-ui/postinstall-deploy.cjs +0 -52
- package/src/plugin-ui/ui-extension-loader/backup.ts +0 -92
- package/src/plugin-ui/ui-extension-loader/index.ts +0 -276
- package/src/plugin-ui/ui-extension-loader/locator.ts +0 -152
- package/src/plugin-ui/ui-extension-loader/manifest.ts +0 -57
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +0 -944
- package/src/plugin-ui/ui-extension-loader/removed-extensions.ts +0 -70
- package/src/plugin-ui/ui-extension-loader/types.ts +0 -72
- package/src/proxy/auth-proxy.ts +0 -414
- package/src/runtime/account.ts +0 -549
- package/src/runtime/index.ts +0 -7
- package/src/runtime/plugin-runtime.ts +0 -94
- package/src/runtime/registry.ts +0 -71
- package/src/runtime/update-checker.ts +0 -286
- package/src/sdk/CLASS_MAP.md +0 -143
- package/src/sdk/index.d.ts +0 -126
- package/src/sdk/index.js +0 -23990
- package/src/sdk/modules/cloud-search-module.js +0 -1117
- package/src/sdk/modules/follow-module.js +0 -1069
- package/src/sdk/modules/group-module.js +0 -7397
- package/src/sdk/modules/relationship-module.js +0 -2269
- package/src/sdk/modules/signaling-module.js +0 -1468
- package/src/sdk/modules/tim-upload-plugin.js +0 -730
- package/src/sdk/node-env/http-request.js +0 -90
- package/src/sdk/node-env/index.js +0 -57
- package/src/sdk/node-env/storage.js +0 -114
- package/src/sdk/package.json +0 -10
- package/src/sdk/tsconfig.json +0 -16
- package/src/state/pending-invocation-store.ts +0 -43
- package/src/state/store.ts +0 -756
- package/src/tim/c2c.ts +0 -451
- package/src/tim/channels.ts +0 -364
- package/src/tim/client.ts +0 -330
- package/src/tim/index.ts +0 -18
- package/src/tim/messages.ts +0 -166
- package/src/tim/sdk-logger-init.ts +0 -50
- package/src/tools.ts +0 -10
- package/src/transport/factory.ts +0 -95
- package/src/transport/oasn/index.ts +0 -17
- package/src/transport/oasn/oasn-agent-card.ts +0 -111
- package/src/transport/oasn/oasn-discovery.ts +0 -108
- package/src/transport/oasn/oasn-files.ts +0 -210
- package/src/transport/oasn/oasn-http.ts +0 -529
- package/src/transport/oasn/oasn-invocation.ts +0 -604
- package/src/transport/oasn/oasn-normalize.ts +0 -133
- package/src/transport/oasn/oasn-register.ts +0 -33
- package/src/transport/oasn/oasn-transport.ts +0 -318
- package/src/transport/oasn/oasn-types.ts +0 -404
- package/src/transport/tim/index.ts +0 -8
- package/src/transport/tim/tim-transport.ts +0 -515
- package/src/transport/types.ts +0 -538
- package/src/types/openclaw-plugin-sdk-media-store.d.ts +0 -9
- package/src/types/openclaw.d.ts +0 -97
- package/src/types/tencentcloud-chat.d.ts +0 -15
- package/src/util/http.ts +0 -113
- package/src/util/logger.ts +0 -131
- 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
|
-
}
|