openclaw-openagent 1.0.1 → 1.0.3
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/index.d.ts +25 -0
- package/dist/index.js +105 -0
- package/dist/src/app/channel-tools.d.ts +28 -0
- package/dist/src/app/channel-tools.js +251 -0
- package/dist/src/app/discovery-tools.d.ts +35 -0
- package/dist/src/app/discovery-tools.js +243 -0
- package/dist/src/app/download-file-tool.d.ts +10 -0
- package/dist/src/app/download-file-tool.js +104 -0
- package/dist/src/app/hooks.d.ts +14 -0
- package/dist/src/app/hooks.js +118 -0
- package/dist/src/app/index.d.ts +13 -0
- package/dist/src/app/index.js +44 -0
- package/dist/src/app/messaging-tools.d.ts +11 -0
- package/dist/src/app/messaging-tools.js +79 -0
- package/dist/src/app/ops-tools.d.ts +21 -0
- package/dist/src/app/ops-tools.js +158 -0
- package/dist/src/app/remote-agent-tool.d.ts +27 -0
- package/dist/src/app/remote-agent-tool.js +461 -0
- package/dist/src/app/types.d.ts +61 -0
- package/dist/src/app/types.js +11 -0
- package/dist/src/app/upload-file-tool.d.ts +16 -0
- package/dist/src/app/upload-file-tool.js +353 -0
- package/dist/src/app/verbose-preflight.d.ts +2 -0
- package/dist/src/app/verbose-preflight.js +145 -0
- package/dist/src/auth/config.d.ts +79 -0
- package/dist/src/auth/config.js +133 -0
- package/dist/src/auth/credential-manager.d.ts +65 -0
- package/dist/src/auth/credential-manager.js +122 -0
- package/dist/src/auth/index.d.ts +6 -0
- package/dist/src/auth/index.js +6 -0
- package/dist/src/auth/verify.d.ts +42 -0
- package/dist/src/auth/verify.js +60 -0
- package/dist/src/channel.d.ts +269 -0
- package/dist/src/channel.js +488 -0
- package/dist/src/compat.d.ts +37 -0
- package/dist/src/compat.js +70 -0
- package/dist/src/config/config-schema.d.ts +56 -0
- package/dist/src/config/config-schema.js +34 -0
- package/dist/src/messaging/aggregator.d.ts +25 -0
- package/dist/src/messaging/aggregator.js +90 -0
- package/dist/src/messaging/collector.d.ts +27 -0
- package/dist/src/messaging/collector.js +76 -0
- package/dist/src/messaging/executor.d.ts +14 -0
- package/dist/src/messaging/executor.js +59 -0
- package/dist/src/messaging/inbound.d.ts +97 -0
- package/dist/src/messaging/inbound.js +63 -0
- package/dist/src/messaging/index.d.ts +10 -0
- package/dist/src/messaging/index.js +9 -0
- package/dist/src/messaging/mention-protocol.d.ts +42 -0
- package/dist/src/messaging/mention-protocol.js +74 -0
- package/dist/src/messaging/process-c2c-request.d.ts +55 -0
- package/dist/src/messaging/process-c2c-request.js +445 -0
- package/dist/src/messaging/process-message.d.ts +62 -0
- package/dist/src/messaging/process-message.js +282 -0
- package/dist/src/messaging/scheduler.d.ts +17 -0
- package/dist/src/messaging/scheduler.js +47 -0
- package/dist/src/messaging/types.d.ts +34 -0
- package/dist/src/messaging/types.js +4 -0
- package/dist/src/plugin-ui/assets/openagent-override.js +9278 -0
- package/dist/src/plugin-ui/index.d.ts +13 -0
- package/dist/src/plugin-ui/index.js +16 -0
- package/dist/src/plugin-ui/ui-extension-loader/backup.d.ts +23 -0
- package/dist/src/plugin-ui/ui-extension-loader/backup.js +82 -0
- package/dist/src/plugin-ui/ui-extension-loader/index.d.ts +28 -0
- package/dist/src/plugin-ui/ui-extension-loader/index.js +240 -0
- package/dist/src/plugin-ui/ui-extension-loader/locator.d.ts +35 -0
- package/dist/src/plugin-ui/ui-extension-loader/locator.js +129 -0
- package/dist/src/plugin-ui/ui-extension-loader/manifest.d.ts +26 -0
- package/dist/src/plugin-ui/ui-extension-loader/manifest.js +45 -0
- package/dist/src/plugin-ui/ui-extension-loader/registry-regex.d.ts +9 -0
- package/dist/src/plugin-ui/ui-extension-loader/registry-regex.js +724 -0
- package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.d.ts +8 -0
- package/dist/src/plugin-ui/ui-extension-loader/removed-extensions.js +58 -0
- package/dist/src/plugin-ui/ui-extension-loader/types.d.ts +55 -0
- package/dist/src/plugin-ui/ui-extension-loader/types.js +23 -0
- package/dist/src/proxy/auth-proxy.d.ts +20 -0
- package/dist/src/proxy/auth-proxy.js +337 -0
- package/dist/src/runtime/account.d.ts +156 -0
- package/dist/src/runtime/account.js +491 -0
- package/dist/src/runtime/index.d.ts +5 -0
- package/dist/src/runtime/index.js +6 -0
- package/dist/src/runtime/plugin-runtime.d.ts +40 -0
- package/dist/src/runtime/plugin-runtime.js +72 -0
- package/dist/src/runtime/registry.d.ts +41 -0
- package/dist/src/runtime/registry.js +60 -0
- package/dist/src/sdk/CLASS_MAP.md +143 -0
- package/dist/src/sdk/index.d.ts +126 -0
- package/dist/src/sdk/index.js +23990 -0
- package/dist/src/sdk/modules/cloud-search-module.js +1117 -0
- package/dist/src/sdk/modules/follow-module.js +1069 -0
- package/dist/src/sdk/modules/group-module.js +7397 -0
- package/dist/src/sdk/modules/relationship-module.js +2269 -0
- package/dist/src/sdk/modules/signaling-module.js +1468 -0
- package/dist/src/sdk/modules/tim-upload-plugin.js +730 -0
- package/dist/src/sdk/node-env/http-request.js +90 -0
- package/dist/src/sdk/node-env/index.js +57 -0
- package/dist/src/sdk/node-env/storage.js +114 -0
- package/dist/src/sdk/package.json +10 -0
- package/dist/src/sdk/sdk/CLASS_MAP.md +143 -0
- package/dist/src/sdk/sdk/index.d.ts +126 -0
- package/dist/src/sdk/sdk/index.js +23990 -0
- package/dist/src/sdk/sdk/modules/cloud-search-module.js +1117 -0
- package/dist/src/sdk/sdk/modules/follow-module.js +1069 -0
- package/dist/src/sdk/sdk/modules/group-module.js +7397 -0
- package/dist/src/sdk/sdk/modules/relationship-module.js +2269 -0
- package/dist/src/sdk/sdk/modules/signaling-module.js +1468 -0
- package/dist/src/sdk/sdk/modules/tim-upload-plugin.js +730 -0
- package/dist/src/sdk/sdk/node-env/http-request.js +90 -0
- package/dist/src/sdk/sdk/node-env/index.js +57 -0
- package/dist/src/sdk/sdk/node-env/storage.js +114 -0
- package/dist/src/sdk/sdk/package.json +10 -0
- package/dist/src/sdk/sdk/tsconfig.json +16 -0
- package/dist/src/sdk/tsconfig.json +16 -0
- package/dist/src/state/pending-invocation-store.d.ts +26 -0
- package/dist/src/state/pending-invocation-store.js +39 -0
- package/dist/src/state/store.d.ts +165 -0
- package/dist/src/state/store.js +535 -0
- package/dist/src/tim/c2c.d.ts +83 -0
- package/dist/src/tim/c2c.js +318 -0
- package/dist/src/tim/channels.d.ts +95 -0
- package/dist/src/tim/channels.js +279 -0
- package/dist/src/tim/client.d.ts +54 -0
- package/dist/src/tim/client.js +268 -0
- package/dist/src/tim/index.d.ts +6 -0
- package/dist/src/tim/index.js +6 -0
- package/dist/src/tim/messages.d.ts +50 -0
- package/dist/src/tim/messages.js +104 -0
- package/dist/src/tim/sdk-logger-init.d.ts +13 -0
- package/dist/src/tim/sdk-logger-init.js +46 -0
- package/dist/src/tools.d.ts +9 -0
- package/dist/src/tools.js +8 -0
- package/dist/src/transport/factory.d.ts +63 -0
- package/dist/src/transport/factory.js +54 -0
- package/dist/src/transport/oasn/index.d.ts +12 -0
- package/dist/src/transport/oasn/index.js +9 -0
- package/dist/src/transport/oasn/oasn-agent-card.d.ts +38 -0
- package/dist/src/transport/oasn/oasn-agent-card.js +102 -0
- package/dist/src/transport/oasn/oasn-discovery.d.ts +33 -0
- package/dist/src/transport/oasn/oasn-discovery.js +97 -0
- package/dist/src/transport/oasn/oasn-files.d.ts +64 -0
- package/dist/src/transport/oasn/oasn-files.js +174 -0
- package/dist/src/transport/oasn/oasn-http.d.ts +98 -0
- package/dist/src/transport/oasn/oasn-http.js +362 -0
- package/dist/src/transport/oasn/oasn-invocation.d.ts +154 -0
- package/dist/src/transport/oasn/oasn-invocation.js +432 -0
- package/dist/src/transport/oasn/oasn-normalize.d.ts +6 -0
- package/dist/src/transport/oasn/oasn-normalize.js +112 -0
- package/dist/src/transport/oasn/oasn-register.d.ts +19 -0
- package/dist/src/transport/oasn/oasn-register.js +24 -0
- package/dist/src/transport/oasn/oasn-transport.d.ts +114 -0
- package/dist/src/transport/oasn/oasn-transport.js +230 -0
- package/dist/src/transport/oasn/oasn-types.d.ts +331 -0
- package/dist/src/transport/oasn/oasn-types.js +44 -0
- package/dist/src/transport/tim/index.d.ts +7 -0
- package/dist/src/transport/tim/index.js +6 -0
- package/dist/src/transport/tim/tim-transport.d.ts +122 -0
- package/dist/src/transport/tim/tim-transport.js +402 -0
- package/dist/src/transport/types.d.ts +450 -0
- package/dist/src/transport/types.js +38 -0
- package/dist/src/util/http.d.ts +21 -0
- package/dist/src/util/http.js +93 -0
- package/dist/src/util/logger.d.ts +20 -0
- package/dist/src/util/logger.js +100 -0
- package/dist/src/util/url-resolver.d.ts +7 -0
- package/dist/src/util/url-resolver.js +20 -0
- package/index.ts +11 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +7 -4
- package/src/app/download-file-tool.ts +133 -0
- package/src/app/hooks.ts +89 -5
- package/src/app/index.ts +6 -0
- package/src/app/remote-agent-tool.ts +46 -0
- package/src/app/types.ts +1 -0
- package/src/app/upload-file-tool.ts +411 -0
- package/src/plugin-ui/assets/openagent-override.js +15 -66
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -61
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -1
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +14 -3
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +102 -63
- package/src/runtime/account.ts +0 -23
- package/src/transport/oasn/oasn-files.ts +5 -5
- package/src/transport/oasn/oasn-invocation.ts +44 -2
- package/src/transport/oasn/oasn-normalize.ts +0 -26
- package/src/transport/oasn/oasn-register.ts +8 -81
- package/src/transport/oasn/oasn-transport.ts +9 -32
- package/src/transport/oasn/oasn-types.ts +53 -6
- package/src/transport/types.ts +33 -36
- package/src/util/url-resolver.ts +17 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OASN HTTP 客户端
|
|
3
|
+
*
|
|
4
|
+
* 设计依据:
|
|
5
|
+
* - docs/specs/2026-06-16-oasn-transport-abstraction-design.md §11.5(401 撤销)
|
|
6
|
+
* - docs/specs/2026-06-16-oasn-api-module-mapping.md §五(统一错误格式)
|
|
7
|
+
*
|
|
8
|
+
* 职责:
|
|
9
|
+
* 1. 提供 GET/POST/PATCH/multipart 上传/二进制下载 等基础方法
|
|
10
|
+
* 2. 注入 `Authorization: Bearer <api_key>` 头
|
|
11
|
+
* 3. 解析 OASN 统一错误响应(`{ error: { code, message, retryable, request_id } }`)
|
|
12
|
+
* → 抛出 TransportError(保留 retryable / requestId 字段,便于上层判断)
|
|
13
|
+
* 4. 401/UNAUTHENTICATED 时触发 `onCredentialRevoked` 回调(§11.5 撤销路径)
|
|
14
|
+
* 5. 支持自动重试(仅当 `retryable: true` 时,最多 3 次指数退避)
|
|
15
|
+
*
|
|
16
|
+
* 不在此层处理:
|
|
17
|
+
* - SSE 升级(详见 oasn-invocation.ts 的 subscribeToInvocation)
|
|
18
|
+
* - 业务幂等持久化(详见 oasn-invocation.ts + state/store.ts)
|
|
19
|
+
* - file_ref LRU(详见 oasn-files.ts)
|
|
20
|
+
*/
|
|
21
|
+
/** OASN HTTP 客户端配置 */
|
|
22
|
+
export interface OasnHttpConfig {
|
|
23
|
+
/** API base URL,如 `https://api.oasn.ai` */
|
|
24
|
+
apiBase: string;
|
|
25
|
+
/** Bearer token(api_key 或 access_token) */
|
|
26
|
+
token?: string;
|
|
27
|
+
/** 单次请求默认超时(ms) */
|
|
28
|
+
defaultTimeoutMs?: number;
|
|
29
|
+
/** 自动重试最大次数(仅当响应 retryable=true 时) */
|
|
30
|
+
maxRetries?: number;
|
|
31
|
+
/**
|
|
32
|
+
* 401/UNAUTHENTICATED 触发回调。
|
|
33
|
+
* 由 OasnTransport 接入,进一步触发 `credential_expired` 事件 + 清本地凭证(§11.5)。
|
|
34
|
+
*/
|
|
35
|
+
onCredentialRevoked?: () => void;
|
|
36
|
+
}
|
|
37
|
+
/** 请求选项 */
|
|
38
|
+
export interface OasnRequestOptions {
|
|
39
|
+
/** 自定义超时(覆盖 defaultTimeoutMs) */
|
|
40
|
+
timeoutMs?: number;
|
|
41
|
+
/** 自定义额外头(Authorization 由 client 自动注入,无需传入) */
|
|
42
|
+
headers?: Record<string, string>;
|
|
43
|
+
/** 跳过自动重试(如轮询接口自己控制节奏) */
|
|
44
|
+
skipRetry?: boolean;
|
|
45
|
+
/** Abort 信号 */
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
}
|
|
48
|
+
export declare class OasnHttpClient {
|
|
49
|
+
private _config;
|
|
50
|
+
constructor(config: OasnHttpConfig);
|
|
51
|
+
/** 动态更新 token —— 注册成功后由 OasnTransport 调用 */
|
|
52
|
+
setToken(token: string | undefined): void;
|
|
53
|
+
get<T>(path: string, options?: OasnRequestOptions): Promise<T>;
|
|
54
|
+
post<T>(path: string, body: unknown, options?: OasnRequestOptions): Promise<T>;
|
|
55
|
+
patch<T>(path: string, body: unknown, options?: OasnRequestOptions): Promise<T>;
|
|
56
|
+
/**
|
|
57
|
+
* multipart/form-data 上传。
|
|
58
|
+
* 仅支持 file + metadata 字段,不做通用 multipart 支持(OASN 上传场景固定)。
|
|
59
|
+
*/
|
|
60
|
+
postMultipart<T>(path: string, parts: {
|
|
61
|
+
file: {
|
|
62
|
+
content: Buffer;
|
|
63
|
+
filename: string;
|
|
64
|
+
mimeType?: string;
|
|
65
|
+
};
|
|
66
|
+
fields?: Record<string, string>;
|
|
67
|
+
}, options?: OasnRequestOptions): Promise<T>;
|
|
68
|
+
/**
|
|
69
|
+
* 下载二进制内容(用于 Artifact 下载)。
|
|
70
|
+
* 不解析响应,直接返回 ArrayBuffer + 头信息。
|
|
71
|
+
*/
|
|
72
|
+
getBinary(path: string, options?: OasnRequestOptions): Promise<{
|
|
73
|
+
content: ArrayBuffer;
|
|
74
|
+
mimeType: string;
|
|
75
|
+
displayName: string;
|
|
76
|
+
}>;
|
|
77
|
+
/**
|
|
78
|
+
* 暴露底层 raw 请求,用于 SSE 等流式场景(如 oasn-invocation 的 SSE 升级)。
|
|
79
|
+
* 调用方自行处理流式响应。
|
|
80
|
+
*/
|
|
81
|
+
rawHttpRequest(method: string, path: string, headers: Record<string, string>, body?: Buffer | string, options?: OasnRequestOptions): Promise<{
|
|
82
|
+
statusCode: number;
|
|
83
|
+
headers: Record<string, string>;
|
|
84
|
+
body: string;
|
|
85
|
+
}>;
|
|
86
|
+
private _resolveUrl;
|
|
87
|
+
private _buildHeaders;
|
|
88
|
+
/**
|
|
89
|
+
* JSON 请求 —— body 自动 JSON.stringify,响应 JSON.parse。
|
|
90
|
+
* 含重试逻辑(仅当 retryable=true 时)。
|
|
91
|
+
*/
|
|
92
|
+
private _jsonRequest;
|
|
93
|
+
private _rawRequest;
|
|
94
|
+
/** 执行单次 HTTP 请求(不解析响应体格式) */
|
|
95
|
+
private _doRawRequest;
|
|
96
|
+
/** 二进制下载 */
|
|
97
|
+
private _doBinaryRequest;
|
|
98
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OASN HTTP 客户端
|
|
3
|
+
*
|
|
4
|
+
* 设计依据:
|
|
5
|
+
* - docs/specs/2026-06-16-oasn-transport-abstraction-design.md §11.5(401 撤销)
|
|
6
|
+
* - docs/specs/2026-06-16-oasn-api-module-mapping.md §五(统一错误格式)
|
|
7
|
+
*
|
|
8
|
+
* 职责:
|
|
9
|
+
* 1. 提供 GET/POST/PATCH/multipart 上传/二进制下载 等基础方法
|
|
10
|
+
* 2. 注入 `Authorization: Bearer <api_key>` 头
|
|
11
|
+
* 3. 解析 OASN 统一错误响应(`{ error: { code, message, retryable, request_id } }`)
|
|
12
|
+
* → 抛出 TransportError(保留 retryable / requestId 字段,便于上层判断)
|
|
13
|
+
* 4. 401/UNAUTHENTICATED 时触发 `onCredentialRevoked` 回调(§11.5 撤销路径)
|
|
14
|
+
* 5. 支持自动重试(仅当 `retryable: true` 时,最多 3 次指数退避)
|
|
15
|
+
*
|
|
16
|
+
* 不在此层处理:
|
|
17
|
+
* - SSE 升级(详见 oasn-invocation.ts 的 subscribeToInvocation)
|
|
18
|
+
* - 业务幂等持久化(详见 oasn-invocation.ts + state/store.ts)
|
|
19
|
+
* - file_ref LRU(详见 oasn-files.ts)
|
|
20
|
+
*/
|
|
21
|
+
import { request as httpRequest } from 'node:http';
|
|
22
|
+
import { request as httpsRequest } from 'node:https';
|
|
23
|
+
import { URL } from 'node:url';
|
|
24
|
+
import { logger } from '../../util/logger.js';
|
|
25
|
+
import { makeTransportError } from '../types.js';
|
|
26
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
27
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
28
|
+
// ───────────────────────────────────────────────────────────────
|
|
29
|
+
// 错误转换
|
|
30
|
+
// ───────────────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* 把 HTTP 响应(含可能的 OASN 统一错误体)转换为 TransportError。
|
|
33
|
+
* 如果响应非 OASN 统一格式,使用 HTTP 状态码做兜底。
|
|
34
|
+
*/
|
|
35
|
+
function toTransportError(statusCode, rawBody) {
|
|
36
|
+
let code = `HTTP_${statusCode}`;
|
|
37
|
+
let message = rawBody.slice(0, 500);
|
|
38
|
+
let retryable = statusCode >= 500 || statusCode === 429 || statusCode === 408;
|
|
39
|
+
let requestId;
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(rawBody);
|
|
42
|
+
if (parsed?.error?.code) {
|
|
43
|
+
code = parsed.error.code;
|
|
44
|
+
message = parsed.error.message ?? message;
|
|
45
|
+
retryable = parsed.error.retryable ?? retryable;
|
|
46
|
+
requestId = parsed.error.request_id;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// 非 JSON 响应,保留兜底值
|
|
51
|
+
}
|
|
52
|
+
return makeTransportError(code, `[${statusCode}] ${message}`, { retryable, requestId });
|
|
53
|
+
}
|
|
54
|
+
// ───────────────────────────────────────────────────────────────
|
|
55
|
+
// OasnHttpClient
|
|
56
|
+
// ───────────────────────────────────────────────────────────────
|
|
57
|
+
export class OasnHttpClient {
|
|
58
|
+
_config;
|
|
59
|
+
constructor(config) {
|
|
60
|
+
if (!config.apiBase) {
|
|
61
|
+
throw new Error('OasnHttpClient: apiBase is required');
|
|
62
|
+
}
|
|
63
|
+
this._config = {
|
|
64
|
+
apiBase: config.apiBase,
|
|
65
|
+
token: config.token,
|
|
66
|
+
defaultTimeoutMs: config.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
67
|
+
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
68
|
+
onCredentialRevoked: config.onCredentialRevoked,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** 动态更新 token —— 注册成功后由 OasnTransport 调用 */
|
|
72
|
+
setToken(token) {
|
|
73
|
+
this._config.token = token;
|
|
74
|
+
}
|
|
75
|
+
// ─────────────────────────────────────────────────────────────
|
|
76
|
+
// 公共方法
|
|
77
|
+
// ─────────────────────────────────────────────────────────────
|
|
78
|
+
async get(path, options) {
|
|
79
|
+
return this._jsonRequest('GET', path, undefined, options);
|
|
80
|
+
}
|
|
81
|
+
async post(path, body, options) {
|
|
82
|
+
return this._jsonRequest('POST', path, body, options);
|
|
83
|
+
}
|
|
84
|
+
async patch(path, body, options) {
|
|
85
|
+
return this._jsonRequest('PATCH', path, body, options);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* multipart/form-data 上传。
|
|
89
|
+
* 仅支持 file + metadata 字段,不做通用 multipart 支持(OASN 上传场景固定)。
|
|
90
|
+
*/
|
|
91
|
+
async postMultipart(path, parts, options) {
|
|
92
|
+
const boundary = `------oasn-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
93
|
+
const chunks = [];
|
|
94
|
+
// 普通字段
|
|
95
|
+
if (parts.fields) {
|
|
96
|
+
for (const [key, value] of Object.entries(parts.fields)) {
|
|
97
|
+
chunks.push(Buffer.from(`--${boundary}\r\n`
|
|
98
|
+
+ `Content-Disposition: form-data; name="${key}"\r\n\r\n`
|
|
99
|
+
+ `${value}\r\n`));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// 文件字段
|
|
103
|
+
const mime = parts.file.mimeType ?? 'application/octet-stream';
|
|
104
|
+
chunks.push(Buffer.from(`--${boundary}\r\n`
|
|
105
|
+
+ `Content-Disposition: form-data; name="file"; filename="${parts.file.filename}"\r\n`
|
|
106
|
+
+ `Content-Type: ${mime}\r\n\r\n`));
|
|
107
|
+
chunks.push(parts.file.content);
|
|
108
|
+
chunks.push(Buffer.from(`\r\n--${boundary}--\r\n`));
|
|
109
|
+
const body = Buffer.concat(chunks);
|
|
110
|
+
return this._rawRequest('POST', path, body, {
|
|
111
|
+
...options,
|
|
112
|
+
headers: {
|
|
113
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
114
|
+
...options?.headers,
|
|
115
|
+
},
|
|
116
|
+
isJsonBody: false,
|
|
117
|
+
expectJsonResponse: true,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 下载二进制内容(用于 Artifact 下载)。
|
|
122
|
+
* 不解析响应,直接返回 ArrayBuffer + 头信息。
|
|
123
|
+
*/
|
|
124
|
+
async getBinary(path, options) {
|
|
125
|
+
const url = this._resolveUrl(path);
|
|
126
|
+
const headers = this._buildHeaders({});
|
|
127
|
+
const timeoutMs = options?.timeoutMs ?? this._config.defaultTimeoutMs;
|
|
128
|
+
return this._doBinaryRequest(url, headers, timeoutMs, options?.signal);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 暴露底层 raw 请求,用于 SSE 等流式场景(如 oasn-invocation 的 SSE 升级)。
|
|
132
|
+
* 调用方自行处理流式响应。
|
|
133
|
+
*/
|
|
134
|
+
rawHttpRequest(method, path, headers, body, options) {
|
|
135
|
+
const url = this._resolveUrl(path);
|
|
136
|
+
const fullHeaders = this._buildHeaders(headers);
|
|
137
|
+
const timeoutMs = options?.timeoutMs ?? this._config.defaultTimeoutMs;
|
|
138
|
+
return this._doRawRequest(url, method, fullHeaders, body, timeoutMs, options?.signal);
|
|
139
|
+
}
|
|
140
|
+
// ─────────────────────────────────────────────────────────────
|
|
141
|
+
// 内部实现
|
|
142
|
+
// ─────────────────────────────────────────────────────────────
|
|
143
|
+
_resolveUrl(path) {
|
|
144
|
+
if (/^https?:\/\//i.test(path)) {
|
|
145
|
+
return new URL(path);
|
|
146
|
+
}
|
|
147
|
+
// path 必须以 / 开头
|
|
148
|
+
const base = this._config.apiBase.replace(/\/$/, '');
|
|
149
|
+
return new URL(`${base}${path}`);
|
|
150
|
+
}
|
|
151
|
+
_buildHeaders(extra) {
|
|
152
|
+
const headers = {
|
|
153
|
+
Accept: 'application/json',
|
|
154
|
+
...extra,
|
|
155
|
+
};
|
|
156
|
+
if (this._config.token) {
|
|
157
|
+
headers['Authorization'] = `Bearer ${this._config.token}`;
|
|
158
|
+
}
|
|
159
|
+
return headers;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* JSON 请求 —— body 自动 JSON.stringify,响应 JSON.parse。
|
|
163
|
+
* 含重试逻辑(仅当 retryable=true 时)。
|
|
164
|
+
*/
|
|
165
|
+
async _jsonRequest(method, path, body, options) {
|
|
166
|
+
return this._rawRequest(method, path, body, {
|
|
167
|
+
...options,
|
|
168
|
+
isJsonBody: true,
|
|
169
|
+
expectJsonResponse: true,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async _rawRequest(method, path, body, options) {
|
|
173
|
+
const url = this._resolveUrl(path);
|
|
174
|
+
const headers = this._buildHeaders(options?.headers ?? {});
|
|
175
|
+
let bodyBuffer;
|
|
176
|
+
if (body !== undefined) {
|
|
177
|
+
if (options.isJsonBody) {
|
|
178
|
+
const json = typeof body === 'string' ? body : JSON.stringify(body);
|
|
179
|
+
bodyBuffer = Buffer.from(json);
|
|
180
|
+
headers['Content-Type'] = 'application/json';
|
|
181
|
+
headers['Content-Length'] = String(bodyBuffer.length);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
bodyBuffer = body;
|
|
185
|
+
if (!headers['Content-Length']) {
|
|
186
|
+
headers['Content-Length'] = String(bodyBuffer.length);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const maxAttempts = options.skipRetry ? 1 : (this._config.maxRetries + 1);
|
|
191
|
+
let lastErr;
|
|
192
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
193
|
+
try {
|
|
194
|
+
if (attempt > 0) {
|
|
195
|
+
// 指数退避:200ms / 400ms / 800ms(含少量随机抖动)
|
|
196
|
+
const delay = 200 * Math.pow(2, attempt - 1) + Math.floor(Math.random() * 50);
|
|
197
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
198
|
+
logger.info(`[transport/oasn/http] retry ${attempt}/${maxAttempts - 1} ${method} ${path}`);
|
|
199
|
+
}
|
|
200
|
+
const { statusCode, body: respBody } = await this._doRawRequest(url, method, headers, bodyBuffer, options?.timeoutMs ?? this._config.defaultTimeoutMs, options?.signal);
|
|
201
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
202
|
+
if (!options.expectJsonResponse)
|
|
203
|
+
return undefined;
|
|
204
|
+
if (statusCode === 204 || !respBody)
|
|
205
|
+
return undefined;
|
|
206
|
+
try {
|
|
207
|
+
return JSON.parse(respBody);
|
|
208
|
+
}
|
|
209
|
+
catch (parseErr) {
|
|
210
|
+
throw makeTransportError('INVALID_JSON_RESPONSE', `Failed to parse OASN response: ${parseErr.message}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// 错误响应
|
|
214
|
+
const err = toTransportError(statusCode, respBody);
|
|
215
|
+
// §11.5 401/UNAUTHENTICATED 撤销路径
|
|
216
|
+
if (statusCode === 401 || err.code === 'UNAUTHENTICATED') {
|
|
217
|
+
logger.warn(`[transport/oasn/http] 401 UNAUTHENTICATED on ${method} ${path}`);
|
|
218
|
+
this._config.onCredentialRevoked?.();
|
|
219
|
+
throw err; // 401 不重试
|
|
220
|
+
}
|
|
221
|
+
if (!err.retryable || attempt === maxAttempts - 1)
|
|
222
|
+
throw err;
|
|
223
|
+
lastErr = err;
|
|
224
|
+
logger.warn(`[transport/oasn/http] retryable error code=${err.code} on ${method} ${path}, will retry`);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
// 网络层错误(DNS / 连接 / 超时) —— 视为可重试
|
|
228
|
+
const isTransport = err.code !== undefined;
|
|
229
|
+
if (isTransport)
|
|
230
|
+
throw err;
|
|
231
|
+
lastErr = err;
|
|
232
|
+
if (attempt === maxAttempts - 1) {
|
|
233
|
+
throw makeTransportError('NETWORK_ERROR', `${method} ${path} failed: ${err.message}`, { retryable: true });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// 不可达,但 TS 需要
|
|
238
|
+
throw lastErr ?? new Error('Unknown HTTP error');
|
|
239
|
+
}
|
|
240
|
+
/** 执行单次 HTTP 请求(不解析响应体格式) */
|
|
241
|
+
_doRawRequest(url, method, headers, body, timeoutMs, signal) {
|
|
242
|
+
const isHttps = url.protocol === 'https:';
|
|
243
|
+
const requestFn = isHttps ? httpsRequest : httpRequest;
|
|
244
|
+
return new Promise((resolve, reject) => {
|
|
245
|
+
const reqOptions = {
|
|
246
|
+
method,
|
|
247
|
+
headers,
|
|
248
|
+
hostname: url.hostname,
|
|
249
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
250
|
+
path: `${url.pathname}${url.search}`,
|
|
251
|
+
};
|
|
252
|
+
let settled = false;
|
|
253
|
+
let timeoutHandle;
|
|
254
|
+
const finish = (fn) => {
|
|
255
|
+
if (settled)
|
|
256
|
+
return;
|
|
257
|
+
settled = true;
|
|
258
|
+
if (timeoutHandle)
|
|
259
|
+
clearTimeout(timeoutHandle);
|
|
260
|
+
fn();
|
|
261
|
+
};
|
|
262
|
+
const req = requestFn(reqOptions, (res) => {
|
|
263
|
+
const chunks = [];
|
|
264
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
265
|
+
res.on('end', () => {
|
|
266
|
+
const respBody = Buffer.concat(chunks).toString('utf-8');
|
|
267
|
+
const respHeaders = {};
|
|
268
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
269
|
+
if (typeof v === 'string')
|
|
270
|
+
respHeaders[k] = v;
|
|
271
|
+
else if (Array.isArray(v))
|
|
272
|
+
respHeaders[k] = v.join(', ');
|
|
273
|
+
}
|
|
274
|
+
finish(() => resolve({
|
|
275
|
+
statusCode: res.statusCode ?? 0,
|
|
276
|
+
headers: respHeaders,
|
|
277
|
+
body: respBody,
|
|
278
|
+
}));
|
|
279
|
+
});
|
|
280
|
+
res.on('error', (err) => finish(() => reject(err)));
|
|
281
|
+
});
|
|
282
|
+
req.on('error', (err) => finish(() => reject(err)));
|
|
283
|
+
timeoutHandle = setTimeout(() => {
|
|
284
|
+
req.destroy(new Error(`OASN HTTP timeout after ${timeoutMs}ms`));
|
|
285
|
+
}, timeoutMs);
|
|
286
|
+
// Abort 支持
|
|
287
|
+
if (signal) {
|
|
288
|
+
const onAbort = () => {
|
|
289
|
+
req.destroy(new Error('Request aborted'));
|
|
290
|
+
};
|
|
291
|
+
if (signal.aborted)
|
|
292
|
+
onAbort();
|
|
293
|
+
else
|
|
294
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
295
|
+
}
|
|
296
|
+
if (body)
|
|
297
|
+
req.write(body);
|
|
298
|
+
req.end();
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/** 二进制下载 */
|
|
302
|
+
_doBinaryRequest(url, headers, timeoutMs, signal) {
|
|
303
|
+
const isHttps = url.protocol === 'https:';
|
|
304
|
+
const requestFn = isHttps ? httpsRequest : httpRequest;
|
|
305
|
+
return new Promise((resolve, reject) => {
|
|
306
|
+
const reqOptions = {
|
|
307
|
+
method: 'GET',
|
|
308
|
+
headers,
|
|
309
|
+
hostname: url.hostname,
|
|
310
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
311
|
+
path: `${url.pathname}${url.search}`,
|
|
312
|
+
};
|
|
313
|
+
let settled = false;
|
|
314
|
+
let timeoutHandle;
|
|
315
|
+
const finish = (fn) => {
|
|
316
|
+
if (settled)
|
|
317
|
+
return;
|
|
318
|
+
settled = true;
|
|
319
|
+
if (timeoutHandle)
|
|
320
|
+
clearTimeout(timeoutHandle);
|
|
321
|
+
fn();
|
|
322
|
+
};
|
|
323
|
+
const req = requestFn(reqOptions, (res) => {
|
|
324
|
+
const status = res.statusCode ?? 0;
|
|
325
|
+
const chunks = [];
|
|
326
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
327
|
+
res.on('end', () => {
|
|
328
|
+
const buf = Buffer.concat(chunks);
|
|
329
|
+
if (status >= 200 && status < 300) {
|
|
330
|
+
const mime = String(res.headers['content-type'] ?? 'application/octet-stream');
|
|
331
|
+
const disposition = String(res.headers['content-disposition'] ?? '');
|
|
332
|
+
const nameMatch = disposition.match(/filename\*?=([^;]+)/i);
|
|
333
|
+
const displayName = nameMatch
|
|
334
|
+
? decodeURIComponent(nameMatch[1].replace(/^UTF-8''/i, '').replace(/^"|"$/g, ''))
|
|
335
|
+
: 'artifact';
|
|
336
|
+
finish(() => resolve({
|
|
337
|
+
content: buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength),
|
|
338
|
+
mimeType: mime,
|
|
339
|
+
displayName,
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
finish(() => reject(toTransportError(status, buf.toString('utf-8'))));
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
res.on('error', (err) => finish(() => reject(err)));
|
|
347
|
+
});
|
|
348
|
+
req.on('error', (err) => finish(() => reject(err)));
|
|
349
|
+
timeoutHandle = setTimeout(() => {
|
|
350
|
+
req.destroy(new Error(`OASN binary download timeout after ${timeoutMs}ms`));
|
|
351
|
+
}, timeoutMs);
|
|
352
|
+
if (signal) {
|
|
353
|
+
const onAbort = () => req.destroy(new Error('Request aborted'));
|
|
354
|
+
if (signal.aborted)
|
|
355
|
+
onAbort();
|
|
356
|
+
else
|
|
357
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
358
|
+
}
|
|
359
|
+
req.end();
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
import { type ProgressCallback, type TaskHandle, type TaskRequest, type TaskResult } from '../types.js';
|
|
30
|
+
import type { OasnHttpClient } from './oasn-http.js';
|
|
31
|
+
import { type GetInvocationResponse } from './oasn-types.js';
|
|
32
|
+
/** Invocation 持久化钩子(接 StateStore.pending_invocations,§11.1) */
|
|
33
|
+
export interface PendingInvocationStore {
|
|
34
|
+
/** sendTask 前查询:若同一 toolCallId 已有未完成 invocation,返回已存的 idempotency_key */
|
|
35
|
+
getPending(toolCallId: string): {
|
|
36
|
+
idempotency_key: string;
|
|
37
|
+
invocation_id?: string;
|
|
38
|
+
} | null;
|
|
39
|
+
/** sendTask 拿到 invocation_id 后回写 */
|
|
40
|
+
upsertPending(record: {
|
|
41
|
+
tool_call_id: string;
|
|
42
|
+
idempotency_key: string;
|
|
43
|
+
invocation_id?: string;
|
|
44
|
+
session_id?: string;
|
|
45
|
+
target_agent_id?: string;
|
|
46
|
+
status: string;
|
|
47
|
+
}): void;
|
|
48
|
+
/** 终态时清理 */
|
|
49
|
+
markTerminal(invocationId: string, status: string): void;
|
|
50
|
+
/** §11.2 abort 时标记为 abandoned */
|
|
51
|
+
markAbandoned(invocationId: string): void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* subscribeToInvocation 选项。
|
|
55
|
+
* 抽象层屏蔽轮询/SSE 差异:上层只关心 onProgress / onComplete / signal。
|
|
56
|
+
*/
|
|
57
|
+
export interface SubscribeOptions {
|
|
58
|
+
/** 进度推送回调 */
|
|
59
|
+
onProgress?: (progress: {
|
|
60
|
+
content: string;
|
|
61
|
+
raw: GetInvocationResponse['progress'];
|
|
62
|
+
}) => void;
|
|
63
|
+
/** 终态回调(succeeded/failed/timed_out/cancelled) */
|
|
64
|
+
onComplete: (final: GetInvocationResponse) => void;
|
|
65
|
+
/** abort 信号 */
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
/** 总等待时长上限(ms)—— 由 waitForTaskResult 协调后传入 */
|
|
68
|
+
totalTimeoutMs: number;
|
|
69
|
+
/** 服务端首轮建议间隔(来自 createInvocation 响应) */
|
|
70
|
+
initialPollAfterMs?: number;
|
|
71
|
+
}
|
|
72
|
+
/** sendTask 拓展输入(OASN 特有) */
|
|
73
|
+
export interface CreateInvocationOptions {
|
|
74
|
+
/**
|
|
75
|
+
* §11.1:客户端持久化的工具调用 id。提供后会与 PendingInvocationStore 联动:
|
|
76
|
+
* - 若已存在未完成记录 → 复用其 idempotency_key(实现进程崩溃恢复)
|
|
77
|
+
* - 否则生成新 UUID 并落盘
|
|
78
|
+
*
|
|
79
|
+
* 不传时退化为每次随机 UUID(无持久化)。
|
|
80
|
+
*/
|
|
81
|
+
toolCallId?: string;
|
|
82
|
+
/** 期望超时(毫秒),服务端可能裁决(effective_timeout_ms) */
|
|
83
|
+
timeoutMs?: number;
|
|
84
|
+
/** 客户端分组标识,便于服务端聚合排障 */
|
|
85
|
+
clientRequestGroupId?: string;
|
|
86
|
+
}
|
|
87
|
+
export declare class OasnInvocation {
|
|
88
|
+
private readonly _http;
|
|
89
|
+
private readonly _pendingStore?;
|
|
90
|
+
/** invocation_id → effective_timeout_ms(waitForTaskResult §11.3 用) */
|
|
91
|
+
private _effectiveTimeoutMap;
|
|
92
|
+
/** invocation_id → 首轮 next_poll_after_ms */
|
|
93
|
+
private _initialPollMap;
|
|
94
|
+
constructor(_http: OasnHttpClient, _pendingStore?: PendingInvocationStore | undefined);
|
|
95
|
+
/**
|
|
96
|
+
* 创建 Invocation。
|
|
97
|
+
*
|
|
98
|
+
* 返回 TaskHandle —— 含 invocation_id / session_id / effective_timeout_ms / next_poll_after_ms。
|
|
99
|
+
* 上层将此 handle 传给 waitForTaskResult。
|
|
100
|
+
*/
|
|
101
|
+
sendTask(targetAgentId: string, task: TaskRequest, options?: CreateInvocationOptions): Promise<TaskHandle>;
|
|
102
|
+
/**
|
|
103
|
+
* 订阅 Invocation 状态变化,直至终态或 abort/timeout。
|
|
104
|
+
*
|
|
105
|
+
* 当前实现:纯轮询循环。
|
|
106
|
+
* 后续若服务端支持 SSE,可在此函数内部探测升级,外部 API 保持不变。
|
|
107
|
+
*
|
|
108
|
+
* 返回 Promise,在终态/超时/abort 时 resolve(内部不抛错,错误由 onComplete 携带 final.status 判定)。
|
|
109
|
+
*/
|
|
110
|
+
subscribeToInvocation(invocationId: string, options: SubscribeOptions): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* 等待任务结果(终态后转换为 TaskResult)。
|
|
113
|
+
*
|
|
114
|
+
* §11.3 协调:
|
|
115
|
+
* 实际等待时间 = max(本地 timeoutMs, 服务端 effective_timeout_ms)
|
|
116
|
+
* 差距 > 30% 时输出 warn,提示用户调整。
|
|
117
|
+
*/
|
|
118
|
+
waitForTaskResult(handle: TaskHandle, options: {
|
|
119
|
+
timeoutMs: number;
|
|
120
|
+
signal?: AbortSignal;
|
|
121
|
+
onProgress?: ProgressCallback;
|
|
122
|
+
}): Promise<TaskResult>;
|
|
123
|
+
/**
|
|
124
|
+
* 把终态 Invocation 响应转换为 TaskResult。
|
|
125
|
+
*
|
|
126
|
+
* - succeeded → status:'complete' + content=summary + artifacts + webuiContinuation
|
|
127
|
+
* - 其它终态 → status:'error' + content=user_error.message
|
|
128
|
+
*
|
|
129
|
+
* content 的归一路径:
|
|
130
|
+
* inlineResult.summary > visible_result.inline_result_summary > ''
|
|
131
|
+
*
|
|
132
|
+
* webui_continuation 仅在 succeeded 时抽取(失败场景后端不保证存在)。
|
|
133
|
+
*/
|
|
134
|
+
private _toTaskResult;
|
|
135
|
+
/**
|
|
136
|
+
* 从终态响应里抽取 WebUI continuation(camelCase / snake_case 双归一)。
|
|
137
|
+
* 缺必要字段(id / launch_url)时返回 undefined,调用方按 falsy 处理即可。
|
|
138
|
+
*/
|
|
139
|
+
private _toWebuiContinuation;
|
|
140
|
+
/** 终态错误专用 TransportError,便于上层 catch */
|
|
141
|
+
private _terminalError;
|
|
142
|
+
private _toArtifactRef;
|
|
143
|
+
private _normalizeCreateInvocation;
|
|
144
|
+
/** clamp 轮询间隔到合理范围 */
|
|
145
|
+
private _clampPollInterval;
|
|
146
|
+
/** 可被 abort 中断的 sleep */
|
|
147
|
+
private _sleepWithAbort;
|
|
148
|
+
/** abort 场景虚拟终态 —— 不调用服务端 cancel(API 不存在 §11.2) */
|
|
149
|
+
private _buildAbandonedResponse;
|
|
150
|
+
/** timeout 虚拟终态 */
|
|
151
|
+
private _buildTimeoutResponse;
|
|
152
|
+
/** invocation_id 不存在时的虚拟终态 */
|
|
153
|
+
private _build404Response;
|
|
154
|
+
}
|