@ai-agent-forge/plugin-sdk 0.88.13 → 0.88.15

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.
@@ -0,0 +1,154 @@
1
+ /**
2
+ * 公共 agent-task 调度器能力契约(联网检索设计 §5.6,P1b)。
3
+ *
4
+ * 本文件只放**可序列化**的请求/响应词汇与校验入口,不 import 任何运行时实现
5
+ * (`SubagentRuntime` 属于 `plugins/subagent-delegate`,不进 SDK)。提供方把
6
+ * one-shot 的 submit/status/result/cancel/drain 经能力目录暴露为
7
+ * `agent-forge.agent-task-scheduler@1`;跨能力目录统一是异步 request/response,
8
+ * 设计文档里的同步方法签名只是提供方内部语义。
9
+ *
10
+ * 线上词汇(本文件是权威定义,与 `docs/design/联网检索设计.md` §5.6 同步):
11
+ * - `status` 返回 `{version, action:"status", status}` 信封,状态词取
12
+ * {@link AgentTaskSchedulerStatusSnapshotV1}:在途原样(queued/admitted/running),
13
+ * 终态走与 result 相同的投影(drain_timeout→timed_out、runner_lost→failed);
14
+ * 任务不存在或越界一律 `"unknown"`(同型拒绝,不区分二者)。
15
+ * - `result` 未命中(未知/越界 taskId)返回不带 `result` 字段的信封。
16
+ * - `cancel`/`drain` 对未知 taskId 明确失败(抛错冒泡),不同于 status/result 的
17
+ * 同型未命中——取消与等待都要求调用方先拿到 submit 返回的 taskId。
18
+ * - requester 作用域由提供方绑定自己的会话,请求里没有 owner/session 字段。
19
+ */
20
+ import type { PluginErrorV1 } from "./operation.ts";
21
+ import { type AgentTaskChildProfileV1 } from "./public-api.ts";
22
+ /** 能力目录里的声明 id;提供方 manifest 的 `provides` 必须与其一致。 */
23
+ export declare const AGENT_TASK_SCHEDULER_CAPABILITY_ID: "agent-forge.agent-task-scheduler";
24
+ /** 能力契约版本(exact match)。 */
25
+ export declare const AGENT_TASK_SCHEDULER_CAPABILITY_VERSION: 1;
26
+ /**
27
+ * submit prompt 的 UTF-8 字节上限。The host runtime accepts the same ceiling;
28
+ * callers may impose a smaller per-task budget.
29
+ */
30
+ export declare const AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES: number;
31
+ /** 结果 text 投影的 UTF-8 字节上限(容纳 dossier JSON,不是 200 字符摘要)。 */
32
+ export declare const AGENT_TASK_SCHEDULER_MAX_TEXT_BYTES: number;
33
+ /** 子会话执行模式(与 child profile 的 mode 同词汇)。 */
34
+ export type AgentTaskSchedulerRunModeV1 = "readonly" | "full-control";
35
+ /**
36
+ * 子任务的可核验事实(设计 §5.6):由宿主 adapter 在子会话创建后读取。
37
+ * `getActiveTools` 不可用时整个 facts 省略(消费方验收失败),绝不伪造;
38
+ * `model` 在宿主不暴露 `getModel` 时省略。
39
+ */
40
+ export interface AgentTaskSchedulerFactsV1 {
41
+ readonly tools: readonly string[];
42
+ readonly model?: string;
43
+ readonly mode: AgentTaskSchedulerRunModeV1;
44
+ }
45
+ /**
46
+ * Provider-native web-search citation carried by the terminal result. Only the
47
+ * URL and title are provider-attested; page content was not fetched.
48
+ */
49
+ export interface AgentTaskSchedulerCitationV1 {
50
+ readonly url: string;
51
+ readonly title?: string;
52
+ }
53
+ /** one-shot 任务提交请求。 */
54
+ export interface AgentTaskSchedulerSubmitV1 {
55
+ readonly version: 1;
56
+ /** 任务提示词,非空,UTF-8 ≤ {@link AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES}。 */
57
+ readonly prompt: string;
58
+ /** 子任务档案(收窄语义),经公共解析器校验。 */
59
+ readonly profile: AgentTaskChildProfileV1;
60
+ /** 绝对 epoch ms 死线;对齐 SubagentRuntime 的 deadline 语义(tick 强制 timed_out)。 */
61
+ readonly deadlineAt?: number;
62
+ /** 附加 metadata(JSON 可序列化对象);提供方注入契约键时不覆盖这里已带的其他键。 */
63
+ readonly metadata?: Record<string, unknown>;
64
+ }
65
+ /** 终态词汇(result/drain 投影后)。 */
66
+ export type AgentTaskSchedulerResultStatusV1 = "succeeded" | "failed" | "cancelled" | "timed_out";
67
+ /** 终态投影(跨能力目录的公开形状,设计 §5.6 状态表)。 */
68
+ export interface AgentTaskSchedulerResultV1 {
69
+ readonly version: 1;
70
+ readonly taskId: string;
71
+ readonly status: AgentTaskSchedulerResultStatusV1;
72
+ /** 最后一条带文本 assistant 消息,UTF-8 ≤ {@link AGENT_TASK_SCHEDULER_MAX_TEXT_BYTES};无文本省略。 */
73
+ readonly text?: string;
74
+ readonly messageCount: number;
75
+ /** 子会话自身的 sessionId(审计用;P1/P1.5 不用于跨轮 resume)。 */
76
+ readonly sessionId?: string;
77
+ /** 子任务档案落地后的可核验事实;宿主不可核验时省略。 */
78
+ readonly facts?: AgentTaskSchedulerFactsV1;
79
+ /** Provider-native web-search citations from the same terminal assistant message; absent when none. */
80
+ readonly citations?: readonly AgentTaskSchedulerCitationV1[];
81
+ /** 失败/超时的结构化错误(保留 reasonCode/reason);cancelled 无原因文本时省略。 */
82
+ readonly error?: PluginErrorV1;
83
+ }
84
+ /**
85
+ * status 快照词汇:在途状态原样暴露;终态与 result 同投影(drain_timeout→timed_out、
86
+ * runner_lost→failed);未知/越界 taskId 返回 `"unknown"`。
87
+ */
88
+ export type AgentTaskSchedulerStatusSnapshotV1 = "queued" | "admitted" | "running" | AgentTaskSchedulerResultStatusV1 | "unknown";
89
+ /** 能力目录请求 union(全部 version:1,按 action 判别)。 */
90
+ export type AgentTaskSchedulerRequestV1 = {
91
+ readonly version: 1;
92
+ readonly action: "submit";
93
+ readonly request: AgentTaskSchedulerSubmitV1;
94
+ } | {
95
+ readonly version: 1;
96
+ readonly action: "status";
97
+ readonly taskId: string;
98
+ } | {
99
+ readonly version: 1;
100
+ readonly action: "result";
101
+ readonly taskId: string;
102
+ } | {
103
+ readonly version: 1;
104
+ readonly action: "cancel";
105
+ readonly taskId: string;
106
+ readonly reason?: string;
107
+ } | {
108
+ readonly version: 1;
109
+ readonly action: "drain";
110
+ readonly taskId: string;
111
+ readonly timeoutMs?: number;
112
+ };
113
+ /** 能力目录响应 union(与请求同 action 判别;全部 JSON 可序列化)。 */
114
+ export type AgentTaskSchedulerResponseV1 = {
115
+ readonly version: 1;
116
+ readonly action: "submit";
117
+ readonly taskId: string;
118
+ } | {
119
+ readonly version: 1;
120
+ readonly action: "status";
121
+ readonly status: AgentTaskSchedulerStatusSnapshotV1;
122
+ } | {
123
+ readonly version: 1;
124
+ readonly action: "result";
125
+ readonly result?: AgentTaskSchedulerResultV1;
126
+ } | {
127
+ readonly version: 1;
128
+ readonly action: "cancel";
129
+ } | {
130
+ readonly version: 1;
131
+ readonly action: "drain";
132
+ readonly result: AgentTaskSchedulerResultV1;
133
+ };
134
+ /** 校验结果:合法给规范化副本,否则给可直接回给调用方的原因。 */
135
+ export type AgentTaskSchedulerParseResultV1<TValue> = {
136
+ readonly ok: true;
137
+ readonly value: TValue;
138
+ } | {
139
+ readonly ok: false;
140
+ readonly message: string;
141
+ };
142
+ /**
143
+ * 校验一份 submit 请求(设计 §5.6 的入口契约):profile 交公共解析器;prompt 非空且
144
+ * UTF-8 ≤ 256 KiB;deadlineAt 非空时必须是安全整数;metadata 非空时必须是 JSON 可
145
+ * 序列化对象。返回冻结的规范化副本。
146
+ */
147
+ export declare function parseAgentTaskSchedulerSubmitV1(value: unknown): AgentTaskSchedulerParseResultV1<AgentTaskSchedulerSubmitV1>;
148
+ /**
149
+ * 校验一份能力目录请求(按 action 判别):version 必须 1、action 必须在词汇表内;
150
+ * submit 递归走 {@link parseAgentTaskSchedulerSubmitV1};taskId 必须是非空字符串;
151
+ * cancel.reason 是可选字符串、drain.timeoutMs 是可选正安全整数。返回冻结的规范化副本。
152
+ */
153
+ export declare function parseAgentTaskSchedulerRequestV1(value: unknown): AgentTaskSchedulerParseResultV1<AgentTaskSchedulerRequestV1>;
154
+ //# sourceMappingURL=agent-task-scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-task-scheduler.d.ts","sourceRoot":"","sources":["../src/agent-task-scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 公共 agent-task 调度器能力契约(联网检索设计 §5.6,P1b)。\n *\n * 本文件只放**可序列化**的请求/响应词汇与校验入口,不 import 任何运行时实现\n * (`SubagentRuntime` 属于 `plugins/subagent-delegate`,不进 SDK)。提供方把\n * one-shot 的 submit/status/result/cancel/drain 经能力目录暴露为\n * `agent-forge.agent-task-scheduler@1`;跨能力目录统一是异步 request/response,\n * 设计文档里的同步方法签名只是提供方内部语义。\n *\n * 线上词汇(本文件是权威定义,与 `docs/design/联网检索设计.md` §5.6 同步):\n * - `status` 返回 `{version, action:\"status\", status}` 信封,状态词取\n * {@link AgentTaskSchedulerStatusSnapshotV1}:在途原样(queued/admitted/running),\n * 终态走与 result 相同的投影(drain_timeout→timed_out、runner_lost→failed);\n * 任务不存在或越界一律 `\"unknown\"`(同型拒绝,不区分二者)。\n * - `result` 未命中(未知/越界 taskId)返回不带 `result` 字段的信封。\n * - `cancel`/`drain` 对未知 taskId 明确失败(抛错冒泡),不同于 status/result 的\n * 同型未命中——取消与等待都要求调用方先拿到 submit 返回的 taskId。\n * - requester 作用域由提供方绑定自己的会话,请求里没有 owner/session 字段。\n */\n\nimport type { PluginErrorV1 } from \"./operation.ts\";\nimport { type AgentTaskChildProfileV1, parseAgentTaskChildProfileV1 } from \"./public-api.ts\";\n\n/** 能力目录里的声明 id;提供方 manifest 的 `provides` 必须与其一致。 */\nexport const AGENT_TASK_SCHEDULER_CAPABILITY_ID = \"agent-forge.agent-task-scheduler\" as const;\n\n/** 能力契约版本(exact match)。 */\nexport const AGENT_TASK_SCHEDULER_CAPABILITY_VERSION = 1 as const;\n\n/**\n * submit prompt 的 UTF-8 字节上限。The host runtime accepts the same ceiling;\n * callers may impose a smaller per-task budget.\n */\nexport const AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES = 48 * 1024;\n\n/** 结果 text 投影的 UTF-8 字节上限(容纳 dossier JSON,不是 200 字符摘要)。 */\nexport const AGENT_TASK_SCHEDULER_MAX_TEXT_BYTES = 32 * 1024;\n\n/** 子会话执行模式(与 child profile 的 mode 同词汇)。 */\nexport type AgentTaskSchedulerRunModeV1 = \"readonly\" | \"full-control\";\n\n/**\n * 子任务的可核验事实(设计 §5.6):由宿主 adapter 在子会话创建后读取。\n * `getActiveTools` 不可用时整个 facts 省略(消费方验收失败),绝不伪造;\n * `model` 在宿主不暴露 `getModel` 时省略。\n */\nexport interface AgentTaskSchedulerFactsV1 {\n\treadonly tools: readonly string[];\n\treadonly model?: string;\n\treadonly mode: AgentTaskSchedulerRunModeV1;\n}\n\n/**\n * Provider-native web-search citation carried by the terminal result. Only the\n * URL and title are provider-attested; page content was not fetched.\n */\nexport interface AgentTaskSchedulerCitationV1 {\n\treadonly url: string;\n\treadonly title?: string;\n}\n\n/** one-shot 任务提交请求。 */\nexport interface AgentTaskSchedulerSubmitV1 {\n\treadonly version: 1;\n\t/** 任务提示词,非空,UTF-8 ≤ {@link AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES}。 */\n\treadonly prompt: string;\n\t/** 子任务档案(收窄语义),经公共解析器校验。 */\n\treadonly profile: AgentTaskChildProfileV1;\n\t/** 绝对 epoch ms 死线;对齐 SubagentRuntime 的 deadline 语义(tick 强制 timed_out)。 */\n\treadonly deadlineAt?: number;\n\t/** 附加 metadata(JSON 可序列化对象);提供方注入契约键时不覆盖这里已带的其他键。 */\n\treadonly metadata?: Record<string, unknown>;\n}\n\n/** 终态词汇(result/drain 投影后)。 */\nexport type AgentTaskSchedulerResultStatusV1 = \"succeeded\" | \"failed\" | \"cancelled\" | \"timed_out\";\n\n/** 终态投影(跨能力目录的公开形状,设计 §5.6 状态表)。 */\nexport interface AgentTaskSchedulerResultV1 {\n\treadonly version: 1;\n\treadonly taskId: string;\n\treadonly status: AgentTaskSchedulerResultStatusV1;\n\t/** 最后一条带文本 assistant 消息,UTF-8 ≤ {@link AGENT_TASK_SCHEDULER_MAX_TEXT_BYTES};无文本省略。 */\n\treadonly text?: string;\n\treadonly messageCount: number;\n\t/** 子会话自身的 sessionId(审计用;P1/P1.5 不用于跨轮 resume)。 */\n\treadonly sessionId?: string;\n\t/** 子任务档案落地后的可核验事实;宿主不可核验时省略。 */\n\treadonly facts?: AgentTaskSchedulerFactsV1;\n\t/** Provider-native web-search citations from the same terminal assistant message; absent when none. */\n\treadonly citations?: readonly AgentTaskSchedulerCitationV1[];\n\t/** 失败/超时的结构化错误(保留 reasonCode/reason);cancelled 无原因文本时省略。 */\n\treadonly error?: PluginErrorV1;\n}\n\n/**\n * status 快照词汇:在途状态原样暴露;终态与 result 同投影(drain_timeout→timed_out、\n * runner_lost→failed);未知/越界 taskId 返回 `\"unknown\"`。\n */\nexport type AgentTaskSchedulerStatusSnapshotV1 =\n\t| \"queued\"\n\t| \"admitted\"\n\t| \"running\"\n\t| AgentTaskSchedulerResultStatusV1\n\t| \"unknown\";\n\n/** 能力目录请求 union(全部 version:1,按 action 判别)。 */\nexport type AgentTaskSchedulerRequestV1 =\n\t| { readonly version: 1; readonly action: \"submit\"; readonly request: AgentTaskSchedulerSubmitV1 }\n\t| { readonly version: 1; readonly action: \"status\"; readonly taskId: string }\n\t| { readonly version: 1; readonly action: \"result\"; readonly taskId: string }\n\t| { readonly version: 1; readonly action: \"cancel\"; readonly taskId: string; readonly reason?: string }\n\t| { readonly version: 1; readonly action: \"drain\"; readonly taskId: string; readonly timeoutMs?: number };\n\n/** 能力目录响应 union(与请求同 action 判别;全部 JSON 可序列化)。 */\nexport type AgentTaskSchedulerResponseV1 =\n\t| { readonly version: 1; readonly action: \"submit\"; readonly taskId: string }\n\t| { readonly version: 1; readonly action: \"status\"; readonly status: AgentTaskSchedulerStatusSnapshotV1 }\n\t| { readonly version: 1; readonly action: \"result\"; readonly result?: AgentTaskSchedulerResultV1 }\n\t| { readonly version: 1; readonly action: \"cancel\" }\n\t| { readonly version: 1; readonly action: \"drain\"; readonly result: AgentTaskSchedulerResultV1 };\n\n/** 校验结果:合法给规范化副本,否则给可直接回给调用方的原因。 */\nexport type AgentTaskSchedulerParseResultV1<TValue> =\n\t| { readonly ok: true; readonly value: TValue }\n\t| { readonly ok: false; readonly message: string };\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nconst textEncoder = new TextEncoder();\n\nfunction utf8ByteLength(value: string): number {\n\treturn textEncoder.encode(value).length;\n}\n\nconst SUBMIT_KEYS: ReadonlySet<string> = new Set([\"version\", \"prompt\", \"profile\", \"deadlineAt\", \"metadata\"]);\nconst REQUEST_ACTIONS: readonly string[] = [\"submit\", \"status\", \"result\", \"cancel\", \"drain\"];\n\n/**\n * 校验一份 submit 请求(设计 §5.6 的入口契约):profile 交公共解析器;prompt 非空且\n * UTF-8 ≤ 256 KiB;deadlineAt 非空时必须是安全整数;metadata 非空时必须是 JSON 可\n * 序列化对象。返回冻结的规范化副本。\n */\nexport function parseAgentTaskSchedulerSubmitV1(\n\tvalue: unknown,\n): AgentTaskSchedulerParseResultV1<AgentTaskSchedulerSubmitV1> {\n\tif (!isPlainRecord(value)) return { ok: false, message: \"agent-task-scheduler submit must be an object\" };\n\tfor (const key of Object.keys(value)) {\n\t\tif (!SUBMIT_KEYS.has(key)) {\n\t\t\treturn { ok: false, message: `agent-task-scheduler submit has unknown key ${JSON.stringify(key)}` };\n\t\t}\n\t}\n\tif (value.version !== 1) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `agent-task-scheduler submit version must be 1, found ${JSON.stringify(value.version)}`,\n\t\t};\n\t}\n\tconst prompt = value.prompt;\n\tif (typeof prompt !== \"string\" || prompt.trim() === \"\") {\n\t\treturn { ok: false, message: \"agent-task-scheduler submit prompt must be a non-empty string\" };\n\t}\n\tconst promptBytes = utf8ByteLength(prompt);\n\tif (promptBytes > AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage:\n\t\t\t\t`agent-task-scheduler submit prompt of ${promptBytes} UTF-8 bytes exceeds the ` +\n\t\t\t\t`${AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES}-byte limit`,\n\t\t};\n\t}\n\tconst parsedProfile = parseAgentTaskChildProfileV1(value.profile);\n\tif (!parsedProfile.ok)\n\t\treturn { ok: false, message: `agent-task-scheduler submit profile: ${parsedProfile.message}` };\n\tconst rawDeadlineAt = value.deadlineAt;\n\tif (rawDeadlineAt !== undefined && !Number.isSafeInteger(rawDeadlineAt)) {\n\t\treturn { ok: false, message: \"agent-task-scheduler submit deadlineAt must be a safe integer (epoch ms)\" };\n\t}\n\tlet metadata: Record<string, unknown> | undefined;\n\tconst rawMetadata = value.metadata;\n\tif (rawMetadata !== undefined) {\n\t\tif (!isPlainRecord(rawMetadata)) {\n\t\t\treturn { ok: false, message: \"agent-task-scheduler submit metadata must be an object\" };\n\t\t}\n\t\ttry {\n\t\t\tconst serialized = JSON.stringify(rawMetadata);\n\t\t\tif (serialized === undefined) {\n\t\t\t\treturn { ok: false, message: \"agent-task-scheduler submit metadata must be JSON-serializable\" };\n\t\t\t}\n\t\t} catch {\n\t\t\treturn { ok: false, message: \"agent-task-scheduler submit metadata must be JSON-serializable\" };\n\t\t}\n\t\tmetadata = rawMetadata;\n\t}\n\treturn {\n\t\tok: true,\n\t\tvalue: Object.freeze({\n\t\t\tversion: 1 as const,\n\t\t\tprompt,\n\t\t\tprofile: parsedProfile.profile,\n\t\t\t...(rawDeadlineAt === undefined ? {} : { deadlineAt: rawDeadlineAt as number }),\n\t\t\t...(metadata === undefined ? {} : { metadata }),\n\t\t}),\n\t};\n}\n\n/** 非空 taskId 的公共校验文本(status/result/cancel/drain 共用)。 */\nfunction parseTaskId(value: unknown): string | undefined {\n\tif (typeof value !== \"string\" || value.length === 0) return undefined;\n\treturn value;\n}\n\n/**\n * 校验一份能力目录请求(按 action 判别):version 必须 1、action 必须在词汇表内;\n * submit 递归走 {@link parseAgentTaskSchedulerSubmitV1};taskId 必须是非空字符串;\n * cancel.reason 是可选字符串、drain.timeoutMs 是可选正安全整数。返回冻结的规范化副本。\n */\nexport function parseAgentTaskSchedulerRequestV1(\n\tvalue: unknown,\n): AgentTaskSchedulerParseResultV1<AgentTaskSchedulerRequestV1> {\n\tif (!isPlainRecord(value)) return { ok: false, message: \"agent-task-scheduler request must be an object\" };\n\tif (value.version !== 1) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `agent-task-scheduler request version must be 1, found ${JSON.stringify(value.version)}`,\n\t\t};\n\t}\n\tconst action = value.action;\n\tif (typeof action !== \"string\" || !REQUEST_ACTIONS.includes(action)) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `agent-task-scheduler request action must be one of ${REQUEST_ACTIONS.join(\", \")}, found ${JSON.stringify(action)}`,\n\t\t};\n\t}\n\tif (action === \"submit\") {\n\t\tconst parsed = parseAgentTaskSchedulerSubmitV1(value.request);\n\t\tif (!parsed.ok) return parsed;\n\t\treturn {\n\t\t\tok: true,\n\t\t\tvalue: Object.freeze({ version: 1 as const, action: \"submit\" as const, request: parsed.value }),\n\t\t};\n\t}\n\tconst taskId = parseTaskId(value.taskId);\n\tif (taskId === undefined) {\n\t\treturn { ok: false, message: `agent-task-scheduler ${action} requires a non-empty taskId string` };\n\t}\n\tif (action === \"status\") {\n\t\treturn { ok: true, value: Object.freeze({ version: 1 as const, action: \"status\" as const, taskId }) };\n\t}\n\tif (action === \"result\") {\n\t\treturn { ok: true, value: Object.freeze({ version: 1 as const, action: \"result\" as const, taskId }) };\n\t}\n\tif (action === \"cancel\") {\n\t\tconst reason = value.reason;\n\t\tif (reason !== undefined && typeof reason !== \"string\") {\n\t\t\treturn { ok: false, message: \"agent-task-scheduler cancel reason must be a string when present\" };\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\tvalue: Object.freeze({\n\t\t\t\tversion: 1 as const,\n\t\t\t\taction: \"cancel\" as const,\n\t\t\t\ttaskId,\n\t\t\t\t...(reason === undefined ? {} : { reason }),\n\t\t\t}),\n\t\t};\n\t}\n\tconst timeoutMs = value.timeoutMs;\n\tif (\n\t\ttimeoutMs !== undefined &&\n\t\t!(typeof timeoutMs === \"number\" && Number.isSafeInteger(timeoutMs) && timeoutMs > 0)\n\t) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: \"agent-task-scheduler drain timeoutMs must be a positive safe integer when present\",\n\t\t};\n\t}\n\treturn {\n\t\tok: true,\n\t\tvalue: Object.freeze({\n\t\t\tversion: 1 as const,\n\t\t\taction: \"drain\" as const,\n\t\t\ttaskId,\n\t\t\t...(timeoutMs === undefined ? {} : { timeoutMs }),\n\t\t}),\n\t};\n}\n"]}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * 公共 agent-task 调度器能力契约(联网检索设计 §5.6,P1b)。
3
+ *
4
+ * 本文件只放**可序列化**的请求/响应词汇与校验入口,不 import 任何运行时实现
5
+ * (`SubagentRuntime` 属于 `plugins/subagent-delegate`,不进 SDK)。提供方把
6
+ * one-shot 的 submit/status/result/cancel/drain 经能力目录暴露为
7
+ * `agent-forge.agent-task-scheduler@1`;跨能力目录统一是异步 request/response,
8
+ * 设计文档里的同步方法签名只是提供方内部语义。
9
+ *
10
+ * 线上词汇(本文件是权威定义,与 `docs/design/联网检索设计.md` §5.6 同步):
11
+ * - `status` 返回 `{version, action:"status", status}` 信封,状态词取
12
+ * {@link AgentTaskSchedulerStatusSnapshotV1}:在途原样(queued/admitted/running),
13
+ * 终态走与 result 相同的投影(drain_timeout→timed_out、runner_lost→failed);
14
+ * 任务不存在或越界一律 `"unknown"`(同型拒绝,不区分二者)。
15
+ * - `result` 未命中(未知/越界 taskId)返回不带 `result` 字段的信封。
16
+ * - `cancel`/`drain` 对未知 taskId 明确失败(抛错冒泡),不同于 status/result 的
17
+ * 同型未命中——取消与等待都要求调用方先拿到 submit 返回的 taskId。
18
+ * - requester 作用域由提供方绑定自己的会话,请求里没有 owner/session 字段。
19
+ */
20
+ import { parseAgentTaskChildProfileV1 } from "./public-api.js";
21
+ /** 能力目录里的声明 id;提供方 manifest 的 `provides` 必须与其一致。 */
22
+ export const AGENT_TASK_SCHEDULER_CAPABILITY_ID = "agent-forge.agent-task-scheduler";
23
+ /** 能力契约版本(exact match)。 */
24
+ export const AGENT_TASK_SCHEDULER_CAPABILITY_VERSION = 1;
25
+ /**
26
+ * submit prompt 的 UTF-8 字节上限。The host runtime accepts the same ceiling;
27
+ * callers may impose a smaller per-task budget.
28
+ */
29
+ export const AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES = 48 * 1024;
30
+ /** 结果 text 投影的 UTF-8 字节上限(容纳 dossier JSON,不是 200 字符摘要)。 */
31
+ export const AGENT_TASK_SCHEDULER_MAX_TEXT_BYTES = 32 * 1024;
32
+ function isPlainRecord(value) {
33
+ return typeof value === "object" && value !== null && !Array.isArray(value);
34
+ }
35
+ const textEncoder = new TextEncoder();
36
+ function utf8ByteLength(value) {
37
+ return textEncoder.encode(value).length;
38
+ }
39
+ const SUBMIT_KEYS = new Set(["version", "prompt", "profile", "deadlineAt", "metadata"]);
40
+ const REQUEST_ACTIONS = ["submit", "status", "result", "cancel", "drain"];
41
+ /**
42
+ * 校验一份 submit 请求(设计 §5.6 的入口契约):profile 交公共解析器;prompt 非空且
43
+ * UTF-8 ≤ 256 KiB;deadlineAt 非空时必须是安全整数;metadata 非空时必须是 JSON 可
44
+ * 序列化对象。返回冻结的规范化副本。
45
+ */
46
+ export function parseAgentTaskSchedulerSubmitV1(value) {
47
+ if (!isPlainRecord(value))
48
+ return { ok: false, message: "agent-task-scheduler submit must be an object" };
49
+ for (const key of Object.keys(value)) {
50
+ if (!SUBMIT_KEYS.has(key)) {
51
+ return { ok: false, message: `agent-task-scheduler submit has unknown key ${JSON.stringify(key)}` };
52
+ }
53
+ }
54
+ if (value.version !== 1) {
55
+ return {
56
+ ok: false,
57
+ message: `agent-task-scheduler submit version must be 1, found ${JSON.stringify(value.version)}`,
58
+ };
59
+ }
60
+ const prompt = value.prompt;
61
+ if (typeof prompt !== "string" || prompt.trim() === "") {
62
+ return { ok: false, message: "agent-task-scheduler submit prompt must be a non-empty string" };
63
+ }
64
+ const promptBytes = utf8ByteLength(prompt);
65
+ if (promptBytes > AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES) {
66
+ return {
67
+ ok: false,
68
+ message: `agent-task-scheduler submit prompt of ${promptBytes} UTF-8 bytes exceeds the ` +
69
+ `${AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES}-byte limit`,
70
+ };
71
+ }
72
+ const parsedProfile = parseAgentTaskChildProfileV1(value.profile);
73
+ if (!parsedProfile.ok)
74
+ return { ok: false, message: `agent-task-scheduler submit profile: ${parsedProfile.message}` };
75
+ const rawDeadlineAt = value.deadlineAt;
76
+ if (rawDeadlineAt !== undefined && !Number.isSafeInteger(rawDeadlineAt)) {
77
+ return { ok: false, message: "agent-task-scheduler submit deadlineAt must be a safe integer (epoch ms)" };
78
+ }
79
+ let metadata;
80
+ const rawMetadata = value.metadata;
81
+ if (rawMetadata !== undefined) {
82
+ if (!isPlainRecord(rawMetadata)) {
83
+ return { ok: false, message: "agent-task-scheduler submit metadata must be an object" };
84
+ }
85
+ try {
86
+ const serialized = JSON.stringify(rawMetadata);
87
+ if (serialized === undefined) {
88
+ return { ok: false, message: "agent-task-scheduler submit metadata must be JSON-serializable" };
89
+ }
90
+ }
91
+ catch {
92
+ return { ok: false, message: "agent-task-scheduler submit metadata must be JSON-serializable" };
93
+ }
94
+ metadata = rawMetadata;
95
+ }
96
+ return {
97
+ ok: true,
98
+ value: Object.freeze({
99
+ version: 1,
100
+ prompt,
101
+ profile: parsedProfile.profile,
102
+ ...(rawDeadlineAt === undefined ? {} : { deadlineAt: rawDeadlineAt }),
103
+ ...(metadata === undefined ? {} : { metadata }),
104
+ }),
105
+ };
106
+ }
107
+ /** 非空 taskId 的公共校验文本(status/result/cancel/drain 共用)。 */
108
+ function parseTaskId(value) {
109
+ if (typeof value !== "string" || value.length === 0)
110
+ return undefined;
111
+ return value;
112
+ }
113
+ /**
114
+ * 校验一份能力目录请求(按 action 判别):version 必须 1、action 必须在词汇表内;
115
+ * submit 递归走 {@link parseAgentTaskSchedulerSubmitV1};taskId 必须是非空字符串;
116
+ * cancel.reason 是可选字符串、drain.timeoutMs 是可选正安全整数。返回冻结的规范化副本。
117
+ */
118
+ export function parseAgentTaskSchedulerRequestV1(value) {
119
+ if (!isPlainRecord(value))
120
+ return { ok: false, message: "agent-task-scheduler request must be an object" };
121
+ if (value.version !== 1) {
122
+ return {
123
+ ok: false,
124
+ message: `agent-task-scheduler request version must be 1, found ${JSON.stringify(value.version)}`,
125
+ };
126
+ }
127
+ const action = value.action;
128
+ if (typeof action !== "string" || !REQUEST_ACTIONS.includes(action)) {
129
+ return {
130
+ ok: false,
131
+ message: `agent-task-scheduler request action must be one of ${REQUEST_ACTIONS.join(", ")}, found ${JSON.stringify(action)}`,
132
+ };
133
+ }
134
+ if (action === "submit") {
135
+ const parsed = parseAgentTaskSchedulerSubmitV1(value.request);
136
+ if (!parsed.ok)
137
+ return parsed;
138
+ return {
139
+ ok: true,
140
+ value: Object.freeze({ version: 1, action: "submit", request: parsed.value }),
141
+ };
142
+ }
143
+ const taskId = parseTaskId(value.taskId);
144
+ if (taskId === undefined) {
145
+ return { ok: false, message: `agent-task-scheduler ${action} requires a non-empty taskId string` };
146
+ }
147
+ if (action === "status") {
148
+ return { ok: true, value: Object.freeze({ version: 1, action: "status", taskId }) };
149
+ }
150
+ if (action === "result") {
151
+ return { ok: true, value: Object.freeze({ version: 1, action: "result", taskId }) };
152
+ }
153
+ if (action === "cancel") {
154
+ const reason = value.reason;
155
+ if (reason !== undefined && typeof reason !== "string") {
156
+ return { ok: false, message: "agent-task-scheduler cancel reason must be a string when present" };
157
+ }
158
+ return {
159
+ ok: true,
160
+ value: Object.freeze({
161
+ version: 1,
162
+ action: "cancel",
163
+ taskId,
164
+ ...(reason === undefined ? {} : { reason }),
165
+ }),
166
+ };
167
+ }
168
+ const timeoutMs = value.timeoutMs;
169
+ if (timeoutMs !== undefined &&
170
+ !(typeof timeoutMs === "number" && Number.isSafeInteger(timeoutMs) && timeoutMs > 0)) {
171
+ return {
172
+ ok: false,
173
+ message: "agent-task-scheduler drain timeoutMs must be a positive safe integer when present",
174
+ };
175
+ }
176
+ return {
177
+ ok: true,
178
+ value: Object.freeze({
179
+ version: 1,
180
+ action: "drain",
181
+ taskId,
182
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
183
+ }),
184
+ };
185
+ }
186
+ //# sourceMappingURL=agent-task-scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-task-scheduler.js","sourceRoot":"","sources":["../src/agent-task-scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 公共 agent-task 调度器能力契约(联网检索设计 §5.6,P1b)。\n *\n * 本文件只放**可序列化**的请求/响应词汇与校验入口,不 import 任何运行时实现\n * (`SubagentRuntime` 属于 `plugins/subagent-delegate`,不进 SDK)。提供方把\n * one-shot 的 submit/status/result/cancel/drain 经能力目录暴露为\n * `agent-forge.agent-task-scheduler@1`;跨能力目录统一是异步 request/response,\n * 设计文档里的同步方法签名只是提供方内部语义。\n *\n * 线上词汇(本文件是权威定义,与 `docs/design/联网检索设计.md` §5.6 同步):\n * - `status` 返回 `{version, action:\"status\", status}` 信封,状态词取\n * {@link AgentTaskSchedulerStatusSnapshotV1}:在途原样(queued/admitted/running),\n * 终态走与 result 相同的投影(drain_timeout→timed_out、runner_lost→failed);\n * 任务不存在或越界一律 `\"unknown\"`(同型拒绝,不区分二者)。\n * - `result` 未命中(未知/越界 taskId)返回不带 `result` 字段的信封。\n * - `cancel`/`drain` 对未知 taskId 明确失败(抛错冒泡),不同于 status/result 的\n * 同型未命中——取消与等待都要求调用方先拿到 submit 返回的 taskId。\n * - requester 作用域由提供方绑定自己的会话,请求里没有 owner/session 字段。\n */\n\nimport type { PluginErrorV1 } from \"./operation.ts\";\nimport { type AgentTaskChildProfileV1, parseAgentTaskChildProfileV1 } from \"./public-api.ts\";\n\n/** 能力目录里的声明 id;提供方 manifest 的 `provides` 必须与其一致。 */\nexport const AGENT_TASK_SCHEDULER_CAPABILITY_ID = \"agent-forge.agent-task-scheduler\" as const;\n\n/** 能力契约版本(exact match)。 */\nexport const AGENT_TASK_SCHEDULER_CAPABILITY_VERSION = 1 as const;\n\n/**\n * submit prompt 的 UTF-8 字节上限。The host runtime accepts the same ceiling;\n * callers may impose a smaller per-task budget.\n */\nexport const AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES = 48 * 1024;\n\n/** 结果 text 投影的 UTF-8 字节上限(容纳 dossier JSON,不是 200 字符摘要)。 */\nexport const AGENT_TASK_SCHEDULER_MAX_TEXT_BYTES = 32 * 1024;\n\n/** 子会话执行模式(与 child profile 的 mode 同词汇)。 */\nexport type AgentTaskSchedulerRunModeV1 = \"readonly\" | \"full-control\";\n\n/**\n * 子任务的可核验事实(设计 §5.6):由宿主 adapter 在子会话创建后读取。\n * `getActiveTools` 不可用时整个 facts 省略(消费方验收失败),绝不伪造;\n * `model` 在宿主不暴露 `getModel` 时省略。\n */\nexport interface AgentTaskSchedulerFactsV1 {\n\treadonly tools: readonly string[];\n\treadonly model?: string;\n\treadonly mode: AgentTaskSchedulerRunModeV1;\n}\n\n/**\n * Provider-native web-search citation carried by the terminal result. Only the\n * URL and title are provider-attested; page content was not fetched.\n */\nexport interface AgentTaskSchedulerCitationV1 {\n\treadonly url: string;\n\treadonly title?: string;\n}\n\n/** one-shot 任务提交请求。 */\nexport interface AgentTaskSchedulerSubmitV1 {\n\treadonly version: 1;\n\t/** 任务提示词,非空,UTF-8 ≤ {@link AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES}。 */\n\treadonly prompt: string;\n\t/** 子任务档案(收窄语义),经公共解析器校验。 */\n\treadonly profile: AgentTaskChildProfileV1;\n\t/** 绝对 epoch ms 死线;对齐 SubagentRuntime 的 deadline 语义(tick 强制 timed_out)。 */\n\treadonly deadlineAt?: number;\n\t/** 附加 metadata(JSON 可序列化对象);提供方注入契约键时不覆盖这里已带的其他键。 */\n\treadonly metadata?: Record<string, unknown>;\n}\n\n/** 终态词汇(result/drain 投影后)。 */\nexport type AgentTaskSchedulerResultStatusV1 = \"succeeded\" | \"failed\" | \"cancelled\" | \"timed_out\";\n\n/** 终态投影(跨能力目录的公开形状,设计 §5.6 状态表)。 */\nexport interface AgentTaskSchedulerResultV1 {\n\treadonly version: 1;\n\treadonly taskId: string;\n\treadonly status: AgentTaskSchedulerResultStatusV1;\n\t/** 最后一条带文本 assistant 消息,UTF-8 ≤ {@link AGENT_TASK_SCHEDULER_MAX_TEXT_BYTES};无文本省略。 */\n\treadonly text?: string;\n\treadonly messageCount: number;\n\t/** 子会话自身的 sessionId(审计用;P1/P1.5 不用于跨轮 resume)。 */\n\treadonly sessionId?: string;\n\t/** 子任务档案落地后的可核验事实;宿主不可核验时省略。 */\n\treadonly facts?: AgentTaskSchedulerFactsV1;\n\t/** Provider-native web-search citations from the same terminal assistant message; absent when none. */\n\treadonly citations?: readonly AgentTaskSchedulerCitationV1[];\n\t/** 失败/超时的结构化错误(保留 reasonCode/reason);cancelled 无原因文本时省略。 */\n\treadonly error?: PluginErrorV1;\n}\n\n/**\n * status 快照词汇:在途状态原样暴露;终态与 result 同投影(drain_timeout→timed_out、\n * runner_lost→failed);未知/越界 taskId 返回 `\"unknown\"`。\n */\nexport type AgentTaskSchedulerStatusSnapshotV1 =\n\t| \"queued\"\n\t| \"admitted\"\n\t| \"running\"\n\t| AgentTaskSchedulerResultStatusV1\n\t| \"unknown\";\n\n/** 能力目录请求 union(全部 version:1,按 action 判别)。 */\nexport type AgentTaskSchedulerRequestV1 =\n\t| { readonly version: 1; readonly action: \"submit\"; readonly request: AgentTaskSchedulerSubmitV1 }\n\t| { readonly version: 1; readonly action: \"status\"; readonly taskId: string }\n\t| { readonly version: 1; readonly action: \"result\"; readonly taskId: string }\n\t| { readonly version: 1; readonly action: \"cancel\"; readonly taskId: string; readonly reason?: string }\n\t| { readonly version: 1; readonly action: \"drain\"; readonly taskId: string; readonly timeoutMs?: number };\n\n/** 能力目录响应 union(与请求同 action 判别;全部 JSON 可序列化)。 */\nexport type AgentTaskSchedulerResponseV1 =\n\t| { readonly version: 1; readonly action: \"submit\"; readonly taskId: string }\n\t| { readonly version: 1; readonly action: \"status\"; readonly status: AgentTaskSchedulerStatusSnapshotV1 }\n\t| { readonly version: 1; readonly action: \"result\"; readonly result?: AgentTaskSchedulerResultV1 }\n\t| { readonly version: 1; readonly action: \"cancel\" }\n\t| { readonly version: 1; readonly action: \"drain\"; readonly result: AgentTaskSchedulerResultV1 };\n\n/** 校验结果:合法给规范化副本,否则给可直接回给调用方的原因。 */\nexport type AgentTaskSchedulerParseResultV1<TValue> =\n\t| { readonly ok: true; readonly value: TValue }\n\t| { readonly ok: false; readonly message: string };\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nconst textEncoder = new TextEncoder();\n\nfunction utf8ByteLength(value: string): number {\n\treturn textEncoder.encode(value).length;\n}\n\nconst SUBMIT_KEYS: ReadonlySet<string> = new Set([\"version\", \"prompt\", \"profile\", \"deadlineAt\", \"metadata\"]);\nconst REQUEST_ACTIONS: readonly string[] = [\"submit\", \"status\", \"result\", \"cancel\", \"drain\"];\n\n/**\n * 校验一份 submit 请求(设计 §5.6 的入口契约):profile 交公共解析器;prompt 非空且\n * UTF-8 ≤ 256 KiB;deadlineAt 非空时必须是安全整数;metadata 非空时必须是 JSON 可\n * 序列化对象。返回冻结的规范化副本。\n */\nexport function parseAgentTaskSchedulerSubmitV1(\n\tvalue: unknown,\n): AgentTaskSchedulerParseResultV1<AgentTaskSchedulerSubmitV1> {\n\tif (!isPlainRecord(value)) return { ok: false, message: \"agent-task-scheduler submit must be an object\" };\n\tfor (const key of Object.keys(value)) {\n\t\tif (!SUBMIT_KEYS.has(key)) {\n\t\t\treturn { ok: false, message: `agent-task-scheduler submit has unknown key ${JSON.stringify(key)}` };\n\t\t}\n\t}\n\tif (value.version !== 1) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `agent-task-scheduler submit version must be 1, found ${JSON.stringify(value.version)}`,\n\t\t};\n\t}\n\tconst prompt = value.prompt;\n\tif (typeof prompt !== \"string\" || prompt.trim() === \"\") {\n\t\treturn { ok: false, message: \"agent-task-scheduler submit prompt must be a non-empty string\" };\n\t}\n\tconst promptBytes = utf8ByteLength(prompt);\n\tif (promptBytes > AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage:\n\t\t\t\t`agent-task-scheduler submit prompt of ${promptBytes} UTF-8 bytes exceeds the ` +\n\t\t\t\t`${AGENT_TASK_SCHEDULER_MAX_PROMPT_BYTES}-byte limit`,\n\t\t};\n\t}\n\tconst parsedProfile = parseAgentTaskChildProfileV1(value.profile);\n\tif (!parsedProfile.ok)\n\t\treturn { ok: false, message: `agent-task-scheduler submit profile: ${parsedProfile.message}` };\n\tconst rawDeadlineAt = value.deadlineAt;\n\tif (rawDeadlineAt !== undefined && !Number.isSafeInteger(rawDeadlineAt)) {\n\t\treturn { ok: false, message: \"agent-task-scheduler submit deadlineAt must be a safe integer (epoch ms)\" };\n\t}\n\tlet metadata: Record<string, unknown> | undefined;\n\tconst rawMetadata = value.metadata;\n\tif (rawMetadata !== undefined) {\n\t\tif (!isPlainRecord(rawMetadata)) {\n\t\t\treturn { ok: false, message: \"agent-task-scheduler submit metadata must be an object\" };\n\t\t}\n\t\ttry {\n\t\t\tconst serialized = JSON.stringify(rawMetadata);\n\t\t\tif (serialized === undefined) {\n\t\t\t\treturn { ok: false, message: \"agent-task-scheduler submit metadata must be JSON-serializable\" };\n\t\t\t}\n\t\t} catch {\n\t\t\treturn { ok: false, message: \"agent-task-scheduler submit metadata must be JSON-serializable\" };\n\t\t}\n\t\tmetadata = rawMetadata;\n\t}\n\treturn {\n\t\tok: true,\n\t\tvalue: Object.freeze({\n\t\t\tversion: 1 as const,\n\t\t\tprompt,\n\t\t\tprofile: parsedProfile.profile,\n\t\t\t...(rawDeadlineAt === undefined ? {} : { deadlineAt: rawDeadlineAt as number }),\n\t\t\t...(metadata === undefined ? {} : { metadata }),\n\t\t}),\n\t};\n}\n\n/** 非空 taskId 的公共校验文本(status/result/cancel/drain 共用)。 */\nfunction parseTaskId(value: unknown): string | undefined {\n\tif (typeof value !== \"string\" || value.length === 0) return undefined;\n\treturn value;\n}\n\n/**\n * 校验一份能力目录请求(按 action 判别):version 必须 1、action 必须在词汇表内;\n * submit 递归走 {@link parseAgentTaskSchedulerSubmitV1};taskId 必须是非空字符串;\n * cancel.reason 是可选字符串、drain.timeoutMs 是可选正安全整数。返回冻结的规范化副本。\n */\nexport function parseAgentTaskSchedulerRequestV1(\n\tvalue: unknown,\n): AgentTaskSchedulerParseResultV1<AgentTaskSchedulerRequestV1> {\n\tif (!isPlainRecord(value)) return { ok: false, message: \"agent-task-scheduler request must be an object\" };\n\tif (value.version !== 1) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `agent-task-scheduler request version must be 1, found ${JSON.stringify(value.version)}`,\n\t\t};\n\t}\n\tconst action = value.action;\n\tif (typeof action !== \"string\" || !REQUEST_ACTIONS.includes(action)) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `agent-task-scheduler request action must be one of ${REQUEST_ACTIONS.join(\", \")}, found ${JSON.stringify(action)}`,\n\t\t};\n\t}\n\tif (action === \"submit\") {\n\t\tconst parsed = parseAgentTaskSchedulerSubmitV1(value.request);\n\t\tif (!parsed.ok) return parsed;\n\t\treturn {\n\t\t\tok: true,\n\t\t\tvalue: Object.freeze({ version: 1 as const, action: \"submit\" as const, request: parsed.value }),\n\t\t};\n\t}\n\tconst taskId = parseTaskId(value.taskId);\n\tif (taskId === undefined) {\n\t\treturn { ok: false, message: `agent-task-scheduler ${action} requires a non-empty taskId string` };\n\t}\n\tif (action === \"status\") {\n\t\treturn { ok: true, value: Object.freeze({ version: 1 as const, action: \"status\" as const, taskId }) };\n\t}\n\tif (action === \"result\") {\n\t\treturn { ok: true, value: Object.freeze({ version: 1 as const, action: \"result\" as const, taskId }) };\n\t}\n\tif (action === \"cancel\") {\n\t\tconst reason = value.reason;\n\t\tif (reason !== undefined && typeof reason !== \"string\") {\n\t\t\treturn { ok: false, message: \"agent-task-scheduler cancel reason must be a string when present\" };\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\tvalue: Object.freeze({\n\t\t\t\tversion: 1 as const,\n\t\t\t\taction: \"cancel\" as const,\n\t\t\t\ttaskId,\n\t\t\t\t...(reason === undefined ? {} : { reason }),\n\t\t\t}),\n\t\t};\n\t}\n\tconst timeoutMs = value.timeoutMs;\n\tif (\n\t\ttimeoutMs !== undefined &&\n\t\t!(typeof timeoutMs === \"number\" && Number.isSafeInteger(timeoutMs) && timeoutMs > 0)\n\t) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: \"agent-task-scheduler drain timeoutMs must be a positive safe integer when present\",\n\t\t};\n\t}\n\treturn {\n\t\tok: true,\n\t\tvalue: Object.freeze({\n\t\t\tversion: 1 as const,\n\t\t\taction: \"drain\" as const,\n\t\t\ttaskId,\n\t\t\t...(timeoutMs === undefined ? {} : { timeoutMs }),\n\t\t}),\n\t};\n}\n"]}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Browser-fetch capability contract(联网检索设计 §5.9, P1.5).
3
+ *
4
+ * `agent-forge.browser.fetch@1` is a plugin-to-plugin service: a consumer asks
5
+ * `browser-playwright` to load a page with the shared browser profile and
6
+ * return bounded visible text. The capability never returns cookies,
7
+ * localStorage or auth files; profile storage stays inside the browser plugin.
8
+ *
9
+ * The request/result payloads are the public design contract. The capability
10
+ * response wraps them in an explicit `ok` union so a provider can report
11
+ * structured failures (`browser_profile_busy`, `browser_login_required`, …)
12
+ * without inventing an Error subclass that would not survive the capability
13
+ * boundary.
14
+ */
15
+ export declare const BROWSER_FETCH_CAPABILITY_ID = "agent-forge.browser.fetch";
16
+ export declare const BROWSER_FETCH_VERSION_V1 = 1;
17
+ export declare const BROWSER_FETCH_MAX_TEXT_CHARS: number;
18
+ export declare const BROWSER_FETCH_DEFAULT_TIMEOUT_MS = 30000;
19
+ export declare const BROWSER_FETCH_MIN_TIMEOUT_MS = 1000;
20
+ export declare const BROWSER_FETCH_MAX_TIMEOUT_MS = 120000;
21
+ export declare const BROWSER_FETCH_MAX_TITLE_CHARS = 512;
22
+ export declare const BROWSER_FETCH_MAX_ERROR_CHARS = 2048;
23
+ export declare const BROWSER_PROFILE_NAME_MAX_CHARS = 64;
24
+ export type BrowserFetchErrorCodeV1 = "browser_unavailable" | "browser_cancelled" | "browser_timeout" | "browser_fetch_failed" | "browser_profile_busy" | "browser_profile_invalid" | "browser_profile_missing" | "browser_login_required";
25
+ export interface BrowserFetchRequestV1 {
26
+ readonly version: 1;
27
+ readonly url: string;
28
+ /** Named profile; default "default". Never a filesystem path. */
29
+ readonly profile?: string;
30
+ readonly timeoutMs?: number;
31
+ }
32
+ export interface BrowserFetchResultV1 {
33
+ readonly version: 1;
34
+ readonly finalUrl: string;
35
+ readonly title?: string;
36
+ /** Visible page text, bounded to {@link BROWSER_FETCH_MAX_TEXT_CHARS}. */
37
+ readonly text: string;
38
+ readonly truncated: boolean;
39
+ }
40
+ export interface BrowserFetchErrorV1 {
41
+ readonly code: BrowserFetchErrorCodeV1;
42
+ readonly message: string;
43
+ }
44
+ export type BrowserFetchCapabilityResponseV1 = {
45
+ readonly version: 1;
46
+ readonly ok: true;
47
+ readonly result: BrowserFetchResultV1;
48
+ } | {
49
+ readonly version: 1;
50
+ readonly ok: false;
51
+ readonly error: BrowserFetchErrorV1;
52
+ };
53
+ /** Profile names are directory segments, never paths: no separators or dots-only names. */
54
+ export declare function isValidBrowserProfileNameV1(value: unknown): value is string;
55
+ export declare function parseBrowserFetchRequestV1(value: unknown): BrowserFetchRequestV1;
56
+ export declare function parseBrowserFetchResultV1(value: unknown): BrowserFetchResultV1;
57
+ export declare function parseBrowserFetchCapabilityResponseV1(value: unknown): BrowserFetchCapabilityResponseV1;
58
+ //# sourceMappingURL=browser-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-fetch.d.ts","sourceRoot":"","sources":["../src/browser-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,2BAA2B,8BAA8B,CAAC;AACvE,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,4BAA4B,QAAY,CAAC;AACtD,eAAO,MAAM,gCAAgC,QAAS,CAAC;AACvD,eAAO,MAAM,4BAA4B,OAAQ,CAAC;AAClD,eAAO,MAAM,4BAA4B,SAAU,CAAC;AACpD,eAAO,MAAM,6BAA6B,MAAM,CAAC;AACjD,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAClD,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAajD,MAAM,MAAM,uBAAuB,GAChC,qBAAqB,GACrB,mBAAmB,GACnB,iBAAiB,GACjB,sBAAsB,GACtB,sBAAsB,GACtB,yBAAyB,GACzB,yBAAyB,GACzB,wBAAwB,CAAC;AAE5B,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,gCAAgC,GACzC;IAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GACjF;IAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAepF,2FAA2F;AAC3F,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAS3E;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,qBAAqB,CA8BhF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CA2B9E;AAED,wBAAgB,qCAAqC,CAAC,KAAK,EAAE,OAAO,GAAG,gCAAgC,CA8BtG","sourcesContent":["/**\n * Browser-fetch capability contract(联网检索设计 §5.9, P1.5).\n *\n * `agent-forge.browser.fetch@1` is a plugin-to-plugin service: a consumer asks\n * `browser-playwright` to load a page with the shared browser profile and\n * return bounded visible text. The capability never returns cookies,\n * localStorage or auth files; profile storage stays inside the browser plugin.\n *\n * The request/result payloads are the public design contract. The capability\n * response wraps them in an explicit `ok` union so a provider can report\n * structured failures (`browser_profile_busy`, `browser_login_required`, …)\n * without inventing an Error subclass that would not survive the capability\n * boundary.\n */\n\nexport const BROWSER_FETCH_CAPABILITY_ID = \"agent-forge.browser.fetch\";\nexport const BROWSER_FETCH_VERSION_V1 = 1;\nexport const BROWSER_FETCH_MAX_TEXT_CHARS = 16 * 1024;\nexport const BROWSER_FETCH_DEFAULT_TIMEOUT_MS = 30_000;\nexport const BROWSER_FETCH_MIN_TIMEOUT_MS = 1_000;\nexport const BROWSER_FETCH_MAX_TIMEOUT_MS = 120_000;\nexport const BROWSER_FETCH_MAX_TITLE_CHARS = 512;\nexport const BROWSER_FETCH_MAX_ERROR_CHARS = 2048;\nexport const BROWSER_PROFILE_NAME_MAX_CHARS = 64;\n\nconst BROWSER_FETCH_ERROR_CODES: ReadonlySet<string> = new Set<string>([\n\t\"browser_unavailable\",\n\t\"browser_cancelled\",\n\t\"browser_timeout\",\n\t\"browser_fetch_failed\",\n\t\"browser_profile_busy\",\n\t\"browser_profile_invalid\",\n\t\"browser_profile_missing\",\n\t\"browser_login_required\",\n]);\n\nexport type BrowserFetchErrorCodeV1 =\n\t| \"browser_unavailable\"\n\t| \"browser_cancelled\"\n\t| \"browser_timeout\"\n\t| \"browser_fetch_failed\"\n\t| \"browser_profile_busy\"\n\t| \"browser_profile_invalid\"\n\t| \"browser_profile_missing\"\n\t| \"browser_login_required\";\n\nexport interface BrowserFetchRequestV1 {\n\treadonly version: 1;\n\treadonly url: string;\n\t/** Named profile; default \"default\". Never a filesystem path. */\n\treadonly profile?: string;\n\treadonly timeoutMs?: number;\n}\n\nexport interface BrowserFetchResultV1 {\n\treadonly version: 1;\n\treadonly finalUrl: string;\n\treadonly title?: string;\n\t/** Visible page text, bounded to {@link BROWSER_FETCH_MAX_TEXT_CHARS}. */\n\treadonly text: string;\n\treadonly truncated: boolean;\n}\n\nexport interface BrowserFetchErrorV1 {\n\treadonly code: BrowserFetchErrorCodeV1;\n\treadonly message: string;\n}\n\nexport type BrowserFetchCapabilityResponseV1 =\n\t| { readonly version: 1; readonly ok: true; readonly result: BrowserFetchResultV1 }\n\t| { readonly version: 1; readonly ok: false; readonly error: BrowserFetchErrorV1 };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isHttpUrl(value: string): boolean {\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** Profile names are directory segments, never paths: no separators or dots-only names. */\nexport function isValidBrowserProfileNameV1(value: unknown): value is string {\n\treturn (\n\t\ttypeof value === \"string\" &&\n\t\tvalue.length > 0 &&\n\t\tvalue.length <= BROWSER_PROFILE_NAME_MAX_CHARS &&\n\t\t/^[a-z0-9][a-z0-9._-]*$/.test(value) &&\n\t\tvalue !== \".\" &&\n\t\tvalue !== \"..\"\n\t);\n}\n\nexport function parseBrowserFetchRequestV1(value: unknown): BrowserFetchRequestV1 {\n\tif (!isRecord(value)) throw new TypeError(\"browser.fetch request must be an object\");\n\tfor (const key of Object.keys(value)) {\n\t\tif (key !== \"version\" && key !== \"url\" && key !== \"profile\" && key !== \"timeoutMs\") {\n\t\t\tthrow new TypeError(`browser.fetch request has unknown key ${JSON.stringify(key)}`);\n\t\t}\n\t}\n\tif (value.version !== 1)\n\t\tthrow new TypeError(`browser.fetch request version must be 1, got ${String(value.version)}`);\n\tif (typeof value.url !== \"string\" || value.url.length === 0 || value.url.length > 2048 || !isHttpUrl(value.url)) {\n\t\tthrow new TypeError(\"browser.fetch request url must be an http(s) URL of at most 2048 characters\");\n\t}\n\tconst profile = value.profile === undefined ? \"default\" : value.profile;\n\tif (!isValidBrowserProfileNameV1(profile)) {\n\t\tthrow new TypeError(\n\t\t\t`browser.fetch request profile must match [a-z0-9][a-z0-9._-]* and be at most ${BROWSER_PROFILE_NAME_MAX_CHARS} characters`,\n\t\t);\n\t}\n\tconst timeoutMs = value.timeoutMs === undefined ? BROWSER_FETCH_DEFAULT_TIMEOUT_MS : value.timeoutMs;\n\tif (\n\t\ttypeof timeoutMs !== \"number\" ||\n\t\t!Number.isInteger(timeoutMs) ||\n\t\ttimeoutMs < BROWSER_FETCH_MIN_TIMEOUT_MS ||\n\t\ttimeoutMs > BROWSER_FETCH_MAX_TIMEOUT_MS\n\t) {\n\t\tthrow new TypeError(\n\t\t\t`browser.fetch request timeoutMs must be an integer between ${BROWSER_FETCH_MIN_TIMEOUT_MS} and ${BROWSER_FETCH_MAX_TIMEOUT_MS}`,\n\t\t);\n\t}\n\treturn { version: 1, url: value.url, profile, timeoutMs };\n}\n\nexport function parseBrowserFetchResultV1(value: unknown): BrowserFetchResultV1 {\n\tif (!isRecord(value)) throw new TypeError(\"browser.fetch result must be an object\");\n\tif (value.version !== 1) throw new TypeError(`browser.fetch result version must be 1, got ${String(value.version)}`);\n\tif (typeof value.finalUrl !== \"string\" || !isHttpUrl(value.finalUrl)) {\n\t\tthrow new TypeError(\"browser.fetch result finalUrl must be an http(s) URL\");\n\t}\n\tif (\n\t\tvalue.title !== undefined &&\n\t\t(typeof value.title !== \"string\" || value.title.length > BROWSER_FETCH_MAX_TITLE_CHARS)\n\t) {\n\t\tthrow new TypeError(\n\t\t\t`browser.fetch result title must be a string of at most ${BROWSER_FETCH_MAX_TITLE_CHARS} characters`,\n\t\t);\n\t}\n\tif (typeof value.text !== \"string\" || value.text.length > BROWSER_FETCH_MAX_TEXT_CHARS) {\n\t\tthrow new TypeError(\n\t\t\t`browser.fetch result text must be a string of at most ${BROWSER_FETCH_MAX_TEXT_CHARS} characters`,\n\t\t);\n\t}\n\tif (typeof value.truncated !== \"boolean\") throw new TypeError(\"browser.fetch result truncated must be a boolean\");\n\treturn {\n\t\tversion: 1,\n\t\tfinalUrl: value.finalUrl,\n\t\t...(value.title === undefined ? {} : { title: value.title }),\n\t\ttext: value.text,\n\t\ttruncated: value.truncated,\n\t};\n}\n\nexport function parseBrowserFetchCapabilityResponseV1(value: unknown): BrowserFetchCapabilityResponseV1 {\n\tif (!isRecord(value)) throw new TypeError(\"browser.fetch capability response must be an object\");\n\tif (value.version !== 1) {\n\t\tthrow new TypeError(`browser.fetch capability response version must be 1, got ${String(value.version)}`);\n\t}\n\tif (value.ok === true) {\n\t\treturn { version: 1, ok: true, result: parseBrowserFetchResultV1(value.result) };\n\t}\n\tif (value.ok === false) {\n\t\tconst error = value.error;\n\t\tif (!isRecord(error)) throw new TypeError(\"browser.fetch capability error must be an object\");\n\t\tif (typeof error.code !== \"string\" || !BROWSER_FETCH_ERROR_CODES.has(error.code)) {\n\t\t\tthrow new TypeError(`browser.fetch capability error has an unknown code ${JSON.stringify(error.code)}`);\n\t\t}\n\t\tif (\n\t\t\ttypeof error.message !== \"string\" ||\n\t\t\terror.message.length === 0 ||\n\t\t\terror.message.length > BROWSER_FETCH_MAX_ERROR_CHARS\n\t\t) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`browser.fetch capability error message must be 1..${BROWSER_FETCH_MAX_ERROR_CHARS} characters`,\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tok: false,\n\t\t\terror: { code: error.code as BrowserFetchErrorCodeV1, message: error.message },\n\t\t};\n\t}\n\tthrow new TypeError(\"browser.fetch capability response ok must be a boolean\");\n}\n"]}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Browser-fetch capability contract(联网检索设计 §5.9, P1.5).
3
+ *
4
+ * `agent-forge.browser.fetch@1` is a plugin-to-plugin service: a consumer asks
5
+ * `browser-playwright` to load a page with the shared browser profile and
6
+ * return bounded visible text. The capability never returns cookies,
7
+ * localStorage or auth files; profile storage stays inside the browser plugin.
8
+ *
9
+ * The request/result payloads are the public design contract. The capability
10
+ * response wraps them in an explicit `ok` union so a provider can report
11
+ * structured failures (`browser_profile_busy`, `browser_login_required`, …)
12
+ * without inventing an Error subclass that would not survive the capability
13
+ * boundary.
14
+ */
15
+ export const BROWSER_FETCH_CAPABILITY_ID = "agent-forge.browser.fetch";
16
+ export const BROWSER_FETCH_VERSION_V1 = 1;
17
+ export const BROWSER_FETCH_MAX_TEXT_CHARS = 16 * 1024;
18
+ export const BROWSER_FETCH_DEFAULT_TIMEOUT_MS = 30_000;
19
+ export const BROWSER_FETCH_MIN_TIMEOUT_MS = 1_000;
20
+ export const BROWSER_FETCH_MAX_TIMEOUT_MS = 120_000;
21
+ export const BROWSER_FETCH_MAX_TITLE_CHARS = 512;
22
+ export const BROWSER_FETCH_MAX_ERROR_CHARS = 2048;
23
+ export const BROWSER_PROFILE_NAME_MAX_CHARS = 64;
24
+ const BROWSER_FETCH_ERROR_CODES = new Set([
25
+ "browser_unavailable",
26
+ "browser_cancelled",
27
+ "browser_timeout",
28
+ "browser_fetch_failed",
29
+ "browser_profile_busy",
30
+ "browser_profile_invalid",
31
+ "browser_profile_missing",
32
+ "browser_login_required",
33
+ ]);
34
+ function isRecord(value) {
35
+ return typeof value === "object" && value !== null && !Array.isArray(value);
36
+ }
37
+ function isHttpUrl(value) {
38
+ try {
39
+ const url = new URL(value);
40
+ return url.protocol === "http:" || url.protocol === "https:";
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ /** Profile names are directory segments, never paths: no separators or dots-only names. */
47
+ export function isValidBrowserProfileNameV1(value) {
48
+ return (typeof value === "string" &&
49
+ value.length > 0 &&
50
+ value.length <= BROWSER_PROFILE_NAME_MAX_CHARS &&
51
+ /^[a-z0-9][a-z0-9._-]*$/.test(value) &&
52
+ value !== "." &&
53
+ value !== "..");
54
+ }
55
+ export function parseBrowserFetchRequestV1(value) {
56
+ if (!isRecord(value))
57
+ throw new TypeError("browser.fetch request must be an object");
58
+ for (const key of Object.keys(value)) {
59
+ if (key !== "version" && key !== "url" && key !== "profile" && key !== "timeoutMs") {
60
+ throw new TypeError(`browser.fetch request has unknown key ${JSON.stringify(key)}`);
61
+ }
62
+ }
63
+ if (value.version !== 1)
64
+ throw new TypeError(`browser.fetch request version must be 1, got ${String(value.version)}`);
65
+ if (typeof value.url !== "string" || value.url.length === 0 || value.url.length > 2048 || !isHttpUrl(value.url)) {
66
+ throw new TypeError("browser.fetch request url must be an http(s) URL of at most 2048 characters");
67
+ }
68
+ const profile = value.profile === undefined ? "default" : value.profile;
69
+ if (!isValidBrowserProfileNameV1(profile)) {
70
+ throw new TypeError(`browser.fetch request profile must match [a-z0-9][a-z0-9._-]* and be at most ${BROWSER_PROFILE_NAME_MAX_CHARS} characters`);
71
+ }
72
+ const timeoutMs = value.timeoutMs === undefined ? BROWSER_FETCH_DEFAULT_TIMEOUT_MS : value.timeoutMs;
73
+ if (typeof timeoutMs !== "number" ||
74
+ !Number.isInteger(timeoutMs) ||
75
+ timeoutMs < BROWSER_FETCH_MIN_TIMEOUT_MS ||
76
+ timeoutMs > BROWSER_FETCH_MAX_TIMEOUT_MS) {
77
+ throw new TypeError(`browser.fetch request timeoutMs must be an integer between ${BROWSER_FETCH_MIN_TIMEOUT_MS} and ${BROWSER_FETCH_MAX_TIMEOUT_MS}`);
78
+ }
79
+ return { version: 1, url: value.url, profile, timeoutMs };
80
+ }
81
+ export function parseBrowserFetchResultV1(value) {
82
+ if (!isRecord(value))
83
+ throw new TypeError("browser.fetch result must be an object");
84
+ if (value.version !== 1)
85
+ throw new TypeError(`browser.fetch result version must be 1, got ${String(value.version)}`);
86
+ if (typeof value.finalUrl !== "string" || !isHttpUrl(value.finalUrl)) {
87
+ throw new TypeError("browser.fetch result finalUrl must be an http(s) URL");
88
+ }
89
+ if (value.title !== undefined &&
90
+ (typeof value.title !== "string" || value.title.length > BROWSER_FETCH_MAX_TITLE_CHARS)) {
91
+ throw new TypeError(`browser.fetch result title must be a string of at most ${BROWSER_FETCH_MAX_TITLE_CHARS} characters`);
92
+ }
93
+ if (typeof value.text !== "string" || value.text.length > BROWSER_FETCH_MAX_TEXT_CHARS) {
94
+ throw new TypeError(`browser.fetch result text must be a string of at most ${BROWSER_FETCH_MAX_TEXT_CHARS} characters`);
95
+ }
96
+ if (typeof value.truncated !== "boolean")
97
+ throw new TypeError("browser.fetch result truncated must be a boolean");
98
+ return {
99
+ version: 1,
100
+ finalUrl: value.finalUrl,
101
+ ...(value.title === undefined ? {} : { title: value.title }),
102
+ text: value.text,
103
+ truncated: value.truncated,
104
+ };
105
+ }
106
+ export function parseBrowserFetchCapabilityResponseV1(value) {
107
+ if (!isRecord(value))
108
+ throw new TypeError("browser.fetch capability response must be an object");
109
+ if (value.version !== 1) {
110
+ throw new TypeError(`browser.fetch capability response version must be 1, got ${String(value.version)}`);
111
+ }
112
+ if (value.ok === true) {
113
+ return { version: 1, ok: true, result: parseBrowserFetchResultV1(value.result) };
114
+ }
115
+ if (value.ok === false) {
116
+ const error = value.error;
117
+ if (!isRecord(error))
118
+ throw new TypeError("browser.fetch capability error must be an object");
119
+ if (typeof error.code !== "string" || !BROWSER_FETCH_ERROR_CODES.has(error.code)) {
120
+ throw new TypeError(`browser.fetch capability error has an unknown code ${JSON.stringify(error.code)}`);
121
+ }
122
+ if (typeof error.message !== "string" ||
123
+ error.message.length === 0 ||
124
+ error.message.length > BROWSER_FETCH_MAX_ERROR_CHARS) {
125
+ throw new TypeError(`browser.fetch capability error message must be 1..${BROWSER_FETCH_MAX_ERROR_CHARS} characters`);
126
+ }
127
+ return {
128
+ version: 1,
129
+ ok: false,
130
+ error: { code: error.code, message: error.message },
131
+ };
132
+ }
133
+ throw new TypeError("browser.fetch capability response ok must be a boolean");
134
+ }
135
+ //# sourceMappingURL=browser-fetch.js.map