omk-agent-core 0.90.8 → 0.90.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +97 -1
  2. package/dist/agent-loop.d.ts +25 -2
  3. package/dist/agent-loop.d.ts.map +1 -1
  4. package/dist/agent-loop.js +492 -187
  5. package/dist/agent-loop.js.map +1 -1
  6. package/dist/agent.d.ts +29 -7
  7. package/dist/agent.d.ts.map +1 -1
  8. package/dist/agent.js +81 -46
  9. package/dist/agent.js.map +1 -1
  10. package/dist/builtin-tool-resource-claims.d.ts +19 -0
  11. package/dist/builtin-tool-resource-claims.d.ts.map +1 -0
  12. package/dist/builtin-tool-resource-claims.js +200 -0
  13. package/dist/builtin-tool-resource-claims.js.map +1 -0
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +7 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/node-resource-resolver.d.ts +42 -0
  19. package/dist/node-resource-resolver.d.ts.map +1 -0
  20. package/dist/node-resource-resolver.js +149 -0
  21. package/dist/node-resource-resolver.js.map +1 -0
  22. package/dist/node.d.ts +1 -0
  23. package/dist/node.d.ts.map +1 -1
  24. package/dist/node.js +2 -0
  25. package/dist/node.js.map +1 -1
  26. package/dist/path-segments.d.ts +8 -0
  27. package/dist/path-segments.d.ts.map +1 -1
  28. package/dist/path-segments.js +62 -9
  29. package/dist/path-segments.js.map +1 -1
  30. package/dist/plain-data.d.ts +7 -0
  31. package/dist/plain-data.d.ts.map +1 -0
  32. package/dist/plain-data.js +70 -0
  33. package/dist/plain-data.js.map +1 -0
  34. package/dist/tool-dag-scheduler.d.ts +86 -0
  35. package/dist/tool-dag-scheduler.d.ts.map +1 -0
  36. package/dist/tool-dag-scheduler.js +171 -0
  37. package/dist/tool-dag-scheduler.js.map +1 -0
  38. package/dist/tool-execution-boundary.d.ts +52 -0
  39. package/dist/tool-execution-boundary.d.ts.map +1 -0
  40. package/dist/tool-execution-boundary.js +185 -0
  41. package/dist/tool-execution-boundary.js.map +1 -0
  42. package/dist/tool-resource-claims.d.ts +31 -0
  43. package/dist/tool-resource-claims.d.ts.map +1 -0
  44. package/dist/tool-resource-claims.js +128 -0
  45. package/dist/tool-resource-claims.js.map +1 -0
  46. package/dist/tool-timeout.d.ts +96 -0
  47. package/dist/tool-timeout.d.ts.map +1 -0
  48. package/dist/tool-timeout.js +173 -0
  49. package/dist/tool-timeout.js.map +1 -0
  50. package/dist/tool-transcript-integrity.d.ts +65 -0
  51. package/dist/tool-transcript-integrity.d.ts.map +1 -0
  52. package/dist/tool-transcript-integrity.js +223 -0
  53. package/dist/tool-transcript-integrity.js.map +1 -0
  54. package/dist/types.d.ts +219 -10
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/types.js +50 -1
  57. package/dist/types.js.map +1 -1
  58. package/package.json +2 -2
@@ -0,0 +1,185 @@
1
+ import { createImmutableSnapshot } from "./plain-data.js";
2
+ import { createToolResultEnvelope, isToolResultEnvelope } from "./types.js";
3
+ export { createImmutableJsonSnapshot, createImmutableSnapshot, parseJsonValue } from "./plain-data.js";
4
+ /** Race one async extension boundary against the parent run's abort signal. */
5
+ export async function awaitWithAbort(start, signal) {
6
+ if (signal?.aborted)
7
+ return { kind: "aborted" };
8
+ if (signal === undefined)
9
+ return { kind: "completed", value: await start() };
10
+ let notifyAbort;
11
+ const aborted = new Promise((resolve) => {
12
+ notifyAbort = () => resolve({ kind: "aborted" });
13
+ signal.addEventListener("abort", notifyAbort, { once: true });
14
+ });
15
+ if (signal.aborted)
16
+ notifyAbort?.();
17
+ try {
18
+ const operation = Promise.resolve(start()).then((value) => ({ kind: "completed", value }));
19
+ return await Promise.race([operation, aborted]);
20
+ }
21
+ finally {
22
+ if (notifyAbort !== undefined)
23
+ signal.removeEventListener("abort", notifyAbort);
24
+ }
25
+ }
26
+ function createValidatedToolResultSnapshot(result) {
27
+ const snapshot = createImmutableSnapshot(result);
28
+ if (!Array.isArray(snapshot.content))
29
+ throw new TypeError("Tool result content must be an array");
30
+ for (const block of snapshot.content) {
31
+ if (typeof block !== "object" || block === null)
32
+ throw new TypeError("Invalid tool result content block");
33
+ const type = Reflect.get(block, "type");
34
+ if (type === "text" && typeof Reflect.get(block, "text") === "string")
35
+ continue;
36
+ if (type === "image" &&
37
+ typeof Reflect.get(block, "data") === "string" &&
38
+ typeof Reflect.get(block, "mimeType") === "string") {
39
+ continue;
40
+ }
41
+ throw new TypeError("Invalid tool result content block");
42
+ }
43
+ if (snapshot.terminate !== undefined && typeof snapshot.terminate !== "boolean") {
44
+ throw new TypeError("Tool result terminate must be a boolean");
45
+ }
46
+ return snapshot;
47
+ }
48
+ /** Build the immutable terminal committed when a tool timeout wins. */
49
+ export function createTimeoutToolResult(toolName, timeoutMs) {
50
+ return createImmutableSnapshot({
51
+ content: [{ type: "text", text: `Tool "${toolName}" timed out after ${timeoutMs}ms and was terminated.` }],
52
+ details: {
53
+ omk: createToolResultEnvelope({
54
+ synthetic: true,
55
+ disposition: "timeout",
56
+ reason: `Tool "${toolName}" timed out after ${timeoutMs}ms`,
57
+ timeoutMs,
58
+ executionStarted: true,
59
+ }),
60
+ },
61
+ });
62
+ }
63
+ /** Build the immutable terminal committed when parent abort wins. */
64
+ export function createAbortedToolResult(executionStarted) {
65
+ return createImmutableSnapshot({
66
+ content: [{ type: "text", text: "Operation aborted" }],
67
+ details: {
68
+ omk: createToolResultEnvelope({
69
+ synthetic: true,
70
+ disposition: "aborted",
71
+ reason: "Operation aborted",
72
+ executionStarted,
73
+ }),
74
+ },
75
+ });
76
+ }
77
+ export function createErrorToolResult(message) {
78
+ return createImmutableSnapshot({ content: [{ type: "text", text: message }], details: {} });
79
+ }
80
+ /** Commit a real result or immutable timeout/abort result across the after hook boundary. */
81
+ export async function finalizeExecutedToolCall(options) {
82
+ const { currentContext, assistantMessage, prepared, executed, afterToolCall, signal } = options;
83
+ let result = executed.result;
84
+ let isError = executed.isError;
85
+ let syntheticFailure = false;
86
+ let terminalDisposition = executed.terminalDisposition;
87
+ try {
88
+ result = createValidatedToolResultSnapshot(result);
89
+ }
90
+ catch (error) {
91
+ result = createErrorToolResult(`Invalid tool result: ${error instanceof Error ? error.message : String(error)}`);
92
+ isError = true;
93
+ syntheticFailure = true;
94
+ }
95
+ if (terminalDisposition === undefined && signal?.aborted) {
96
+ terminalDisposition = "aborted";
97
+ result = createAbortedToolResult(executed.executionStarted);
98
+ isError = true;
99
+ }
100
+ if (terminalDisposition === undefined && afterToolCall && !syntheticFailure) {
101
+ try {
102
+ const bounded = await awaitWithAbort(() => afterToolCall({
103
+ assistantMessage,
104
+ toolCall: prepared.toolCall,
105
+ args: prepared.args,
106
+ result,
107
+ isError,
108
+ context: currentContext,
109
+ }, signal), signal);
110
+ if (bounded.kind === "aborted" || signal?.aborted) {
111
+ terminalDisposition = "aborted";
112
+ result = createAbortedToolResult(executed.executionStarted);
113
+ isError = true;
114
+ }
115
+ else if (bounded.value) {
116
+ result = {
117
+ content: bounded.value.content ?? result.content,
118
+ details: Object.hasOwn(bounded.value, "details") ? bounded.value.details : result.details,
119
+ terminate: bounded.value.terminate ?? result.terminate,
120
+ };
121
+ isError = bounded.value.isError ?? isError;
122
+ }
123
+ }
124
+ catch (error) {
125
+ result = createErrorToolResult(error instanceof Error ? error.message : String(error));
126
+ isError = true;
127
+ syntheticFailure = true;
128
+ }
129
+ }
130
+ if (terminalDisposition === undefined) {
131
+ try {
132
+ result = createValidatedToolResultSnapshot(result);
133
+ }
134
+ catch (error) {
135
+ result = createErrorToolResult(`Invalid tool result: ${error instanceof Error ? error.message : String(error)}`);
136
+ isError = true;
137
+ syntheticFailure = true;
138
+ }
139
+ }
140
+ else {
141
+ isError = true;
142
+ }
143
+ const envelope = terminalDisposition !== undefined
144
+ ? createToolResultEnvelope({
145
+ disposition: terminalDisposition,
146
+ synthetic: true,
147
+ executionStarted: executed.executionStarted,
148
+ reason: terminalDisposition === "timeout"
149
+ ? `Tool "${prepared.toolCall.name}" timed out after ${prepared.timeoutMs}ms`
150
+ : "Operation aborted",
151
+ ...(terminalDisposition === "timeout" ? { timeoutMs: prepared.timeoutMs } : {}),
152
+ })
153
+ : createToolResultEnvelope({
154
+ disposition: isError ? "failed" : "completed",
155
+ synthetic: syntheticFailure,
156
+ executionStarted: executed.executionStarted,
157
+ });
158
+ return {
159
+ toolCall: prepared.toolCall,
160
+ result,
161
+ isError,
162
+ envelope,
163
+ isRealPromiseSettled: executed.isRealPromiseSettled,
164
+ commitTerminal: executed.commitTerminal,
165
+ };
166
+ }
167
+ function isPlainDetails(details) {
168
+ if (typeof details !== "object" || details === null || Array.isArray(details))
169
+ return false;
170
+ const prototype = Object.getPrototypeOf(details);
171
+ return prototype === Object.prototype || prototype === null;
172
+ }
173
+ /** Preserve compatibility details while replacing any untrusted `omk` field. */
174
+ export function stampToolResultEnvelope(details, envelope) {
175
+ if (!isToolResultEnvelope(envelope))
176
+ throw new TypeError("Refusing to persist an invalid tool-result/v2 envelope");
177
+ if (details === undefined)
178
+ return { omk: envelope };
179
+ if (!isPlainDetails(details))
180
+ return { originalDetails: details, omk: envelope };
181
+ const preserved = { ...details };
182
+ delete preserved.omk;
183
+ return { ...preserved, omk: envelope };
184
+ }
185
+ //# sourceMappingURL=tool-execution-boundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-execution-boundary.js","sourceRoot":"","sources":["../src/tool-execution-boundary.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"omk-ai\";\nimport { createImmutableSnapshot } from \"./plain-data.ts\";\nimport type {\n\tAgentContext,\n\tAgentLoopConfig,\n\tAgentToolCall,\n\tAgentToolResult,\n\tToolResultEnvelope,\n\tToolTimeoutDisposition,\n} from \"./types.ts\";\nimport { createToolResultEnvelope, isToolResultEnvelope } from \"./types.ts\";\n\nexport { createImmutableJsonSnapshot, createImmutableSnapshot, parseJsonValue } from \"./plain-data.ts\";\n\nexport type AbortBoundResult<T> = { kind: \"completed\"; value: T } | { kind: \"aborted\" };\n\n/** Race one async extension boundary against the parent run's abort signal. */\nexport async function awaitWithAbort<T>(\n\tstart: () => Promise<T> | T,\n\tsignal: AbortSignal | undefined,\n): Promise<AbortBoundResult<T>> {\n\tif (signal?.aborted) return { kind: \"aborted\" };\n\tif (signal === undefined) return { kind: \"completed\", value: await start() };\n\n\tlet notifyAbort: (() => void) | undefined;\n\tconst aborted = new Promise<AbortBoundResult<T>>((resolve) => {\n\t\tnotifyAbort = () => resolve({ kind: \"aborted\" });\n\t\tsignal.addEventListener(\"abort\", notifyAbort, { once: true });\n\t});\n\tif (signal.aborted) notifyAbort?.();\n\n\ttry {\n\t\tconst operation = Promise.resolve(start()).then((value): AbortBoundResult<T> => ({ kind: \"completed\", value }));\n\t\treturn await Promise.race([operation, aborted]);\n\t} finally {\n\t\tif (notifyAbort !== undefined) signal.removeEventListener(\"abort\", notifyAbort);\n\t}\n}\n\nexport interface ToolDispositionEnvelope {\n\tomk: ToolResultEnvelope;\n}\n\nfunction createValidatedToolResultSnapshot(result: AgentToolResult<unknown>): AgentToolResult<unknown> {\n\tconst snapshot = createImmutableSnapshot(result);\n\tif (!Array.isArray(snapshot.content)) throw new TypeError(\"Tool result content must be an array\");\n\tfor (const block of snapshot.content) {\n\t\tif (typeof block !== \"object\" || block === null) throw new TypeError(\"Invalid tool result content block\");\n\t\tconst type = Reflect.get(block, \"type\");\n\t\tif (type === \"text\" && typeof Reflect.get(block, \"text\") === \"string\") continue;\n\t\tif (\n\t\t\ttype === \"image\" &&\n\t\t\ttypeof Reflect.get(block, \"data\") === \"string\" &&\n\t\t\ttypeof Reflect.get(block, \"mimeType\") === \"string\"\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tthrow new TypeError(\"Invalid tool result content block\");\n\t}\n\tif (snapshot.terminate !== undefined && typeof snapshot.terminate !== \"boolean\") {\n\t\tthrow new TypeError(\"Tool result terminate must be a boolean\");\n\t}\n\treturn snapshot;\n}\n\n/** Build the immutable terminal committed when a tool timeout wins. */\nexport function createTimeoutToolResult(toolName: string, timeoutMs: number): AgentToolResult<ToolDispositionEnvelope> {\n\treturn createImmutableSnapshot({\n\t\tcontent: [{ type: \"text\", text: `Tool \"${toolName}\" timed out after ${timeoutMs}ms and was terminated.` }],\n\t\tdetails: {\n\t\t\tomk: createToolResultEnvelope({\n\t\t\t\tsynthetic: true,\n\t\t\t\tdisposition: \"timeout\",\n\t\t\t\treason: `Tool \"${toolName}\" timed out after ${timeoutMs}ms`,\n\t\t\t\ttimeoutMs,\n\t\t\t\texecutionStarted: true,\n\t\t\t}),\n\t\t},\n\t});\n}\n\n/** Build the immutable terminal committed when parent abort wins. */\nexport function createAbortedToolResult(executionStarted: boolean): AgentToolResult<ToolDispositionEnvelope> {\n\treturn createImmutableSnapshot({\n\t\tcontent: [{ type: \"text\", text: \"Operation aborted\" }],\n\t\tdetails: {\n\t\t\tomk: createToolResultEnvelope({\n\t\t\t\tsynthetic: true,\n\t\t\t\tdisposition: \"aborted\",\n\t\t\t\treason: \"Operation aborted\",\n\t\t\t\texecutionStarted,\n\t\t\t}),\n\t\t},\n\t});\n}\n\nexport interface ExecutedToolCallOutcome {\n\tresult: AgentToolResult<unknown>;\n\tisError: boolean;\n\texecutionStarted: boolean;\n\tterminalDisposition?: ToolTimeoutDisposition;\n\tisRealPromiseSettled: () => boolean;\n\tcommitTerminal: () => void;\n}\n\nexport interface FinalizedToolCallOutcome {\n\ttoolCall: AgentToolCall;\n\tresult: AgentToolResult<unknown>;\n\tisError: boolean;\n\tenvelope: ToolResultEnvelope;\n\tisRealPromiseSettled?: () => boolean;\n\tcommitTerminal?: () => void;\n}\n\ninterface FinalizeToolCallOptions {\n\tcurrentContext: AgentContext;\n\tassistantMessage: AssistantMessage;\n\tprepared: { toolCall: AgentToolCall; args: unknown; timeoutMs: number };\n\texecuted: ExecutedToolCallOutcome;\n\tafterToolCall: AgentLoopConfig[\"afterToolCall\"];\n\tsignal: AbortSignal | undefined;\n}\n\nexport function createErrorToolResult(message: string): AgentToolResult<unknown> {\n\treturn createImmutableSnapshot({ content: [{ type: \"text\", text: message }], details: {} });\n}\n\n/** Commit a real result or immutable timeout/abort result across the after hook boundary. */\nexport async function finalizeExecutedToolCall(options: FinalizeToolCallOptions): Promise<FinalizedToolCallOutcome> {\n\tconst { currentContext, assistantMessage, prepared, executed, afterToolCall, signal } = options;\n\tlet result = executed.result;\n\tlet isError = executed.isError;\n\tlet syntheticFailure = false;\n\tlet terminalDisposition = executed.terminalDisposition;\n\n\ttry {\n\t\tresult = createValidatedToolResultSnapshot(result);\n\t} catch (error) {\n\t\tresult = createErrorToolResult(`Invalid tool result: ${error instanceof Error ? error.message : String(error)}`);\n\t\tisError = true;\n\t\tsyntheticFailure = true;\n\t}\n\n\tif (terminalDisposition === undefined && signal?.aborted) {\n\t\tterminalDisposition = \"aborted\";\n\t\tresult = createAbortedToolResult(executed.executionStarted);\n\t\tisError = true;\n\t}\n\tif (terminalDisposition === undefined && afterToolCall && !syntheticFailure) {\n\t\ttry {\n\t\t\tconst bounded = await awaitWithAbort(\n\t\t\t\t() =>\n\t\t\t\t\tafterToolCall(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tassistantMessage,\n\t\t\t\t\t\t\ttoolCall: prepared.toolCall,\n\t\t\t\t\t\t\targs: prepared.args,\n\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t\tisError,\n\t\t\t\t\t\t\tcontext: currentContext,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t),\n\t\t\t\tsignal,\n\t\t\t);\n\t\t\tif (bounded.kind === \"aborted\" || signal?.aborted) {\n\t\t\t\tterminalDisposition = \"aborted\";\n\t\t\t\tresult = createAbortedToolResult(executed.executionStarted);\n\t\t\t\tisError = true;\n\t\t\t} else if (bounded.value) {\n\t\t\t\tresult = {\n\t\t\t\t\tcontent: bounded.value.content ?? result.content,\n\t\t\t\t\tdetails: Object.hasOwn(bounded.value, \"details\") ? bounded.value.details : result.details,\n\t\t\t\t\tterminate: bounded.value.terminate ?? result.terminate,\n\t\t\t\t};\n\t\t\t\tisError = bounded.value.isError ?? isError;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tresult = createErrorToolResult(error instanceof Error ? error.message : String(error));\n\t\t\tisError = true;\n\t\t\tsyntheticFailure = true;\n\t\t}\n\t}\n\n\tif (terminalDisposition === undefined) {\n\t\ttry {\n\t\t\tresult = createValidatedToolResultSnapshot(result);\n\t\t} catch (error) {\n\t\t\tresult = createErrorToolResult(\n\t\t\t\t`Invalid tool result: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tisError = true;\n\t\t\tsyntheticFailure = true;\n\t\t}\n\t} else {\n\t\tisError = true;\n\t}\n\tconst envelope =\n\t\tterminalDisposition !== undefined\n\t\t\t? createToolResultEnvelope({\n\t\t\t\t\tdisposition: terminalDisposition,\n\t\t\t\t\tsynthetic: true,\n\t\t\t\t\texecutionStarted: executed.executionStarted,\n\t\t\t\t\treason:\n\t\t\t\t\t\tterminalDisposition === \"timeout\"\n\t\t\t\t\t\t\t? `Tool \"${prepared.toolCall.name}\" timed out after ${prepared.timeoutMs}ms`\n\t\t\t\t\t\t\t: \"Operation aborted\",\n\t\t\t\t\t...(terminalDisposition === \"timeout\" ? { timeoutMs: prepared.timeoutMs } : {}),\n\t\t\t\t})\n\t\t\t: createToolResultEnvelope({\n\t\t\t\t\tdisposition: isError ? \"failed\" : \"completed\",\n\t\t\t\t\tsynthetic: syntheticFailure,\n\t\t\t\t\texecutionStarted: executed.executionStarted,\n\t\t\t\t});\n\treturn {\n\t\ttoolCall: prepared.toolCall,\n\t\tresult,\n\t\tisError,\n\t\tenvelope,\n\t\tisRealPromiseSettled: executed.isRealPromiseSettled,\n\t\tcommitTerminal: executed.commitTerminal,\n\t};\n}\n\nfunction isPlainDetails(details: unknown): details is Record<string, unknown> {\n\tif (typeof details !== \"object\" || details === null || Array.isArray(details)) return false;\n\tconst prototype = Object.getPrototypeOf(details);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\n/** Preserve compatibility details while replacing any untrusted `omk` field. */\nexport function stampToolResultEnvelope(details: unknown, envelope: ToolResultEnvelope): unknown {\n\tif (!isToolResultEnvelope(envelope)) throw new TypeError(\"Refusing to persist an invalid tool-result/v2 envelope\");\n\tif (details === undefined) return { omk: envelope };\n\tif (!isPlainDetails(details)) return { originalDetails: details, omk: envelope };\n\tconst preserved = { ...details };\n\tdelete preserved.omk;\n\treturn { ...preserved, omk: envelope };\n}\n"]}
@@ -0,0 +1,31 @@
1
+ /** Browser-safe resource claims for the opt-in dag-v2 scheduler. */
2
+ import type { ToolParallelPolicy } from "./parallel-tool-batch.ts";
3
+ import type { AgentTool, ResourceKeyResolver, ToolResourceClaim } from "./types.ts";
4
+ export { resolvePathClaimKey, resolveToolClaims } from "./builtin-tool-resource-claims.ts";
5
+ export type { ResourceAccess, ToolResourceAccess, ToolResourceClaim, ToolResourceClaims, ToolResourceClaimsContext, } from "./types.ts";
6
+ export type ToolClaimResolution = {
7
+ kind: "exclusive";
8
+ } | {
9
+ kind: "claims";
10
+ claims: ToolResourceClaim[];
11
+ };
12
+ export interface ClaimableToolCall {
13
+ id?: string;
14
+ name: string;
15
+ arguments: unknown;
16
+ }
17
+ export type RegisteredToolClaimDefinition = Pick<AgentTool, "name" | "executionMode" | "resourceClaims">;
18
+ export interface ResolveToolClaimsOptions {
19
+ cwd: string;
20
+ toolPolicies?: ReadonlyMap<string, ToolParallelPolicy>;
21
+ registeredTools?: readonly RegisteredToolClaimDefinition[];
22
+ strictExtensionClaims?: boolean;
23
+ resourceKeyResolver?: ResourceKeyResolver;
24
+ }
25
+ /** Resolve one call, failing malformed or rejected extension claims closed. */
26
+ export declare function resolveToolClaimsForCall(toolCall: ClaimableToolCall, options: ResolveToolClaimsOptions): Promise<ToolClaimResolution>;
27
+ export declare function compareClaims(left: ToolResourceClaim, right: ToolResourceClaim): number;
28
+ export declare function canonicalizeClaims(claims: readonly ToolResourceClaim[]): ToolResourceClaim[];
29
+ export declare function claimsConflict(left: ToolResourceClaim, right: ToolResourceClaim): boolean;
30
+ export declare function resolutionsConflict(left: ToolClaimResolution, right: ToolClaimResolution): boolean;
31
+ //# sourceMappingURL=tool-resource-claims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-resource-claims.d.ts","sourceRoot":"","sources":["../src/tool-resource-claims.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAYpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAkB,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpG,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3F,YAAY,EACX,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,GACzB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,mBAAmB,GAAG;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAAC;AAE1G,MAAM,WAAW,iBAAiB;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,6BAA6B,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,gBAAgB,CAAC,CAAC;AAEzG,MAAM,WAAW,wBAAwB;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACvD,eAAe,CAAC,EAAE,SAAS,6BAA6B,EAAE,CAAC;IAC3D,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC1C;AAkCD,+EAA+E;AAC/E,wBAAsB,wBAAwB,CAC7C,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,wBAAwB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,CAkC9B;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAWvF;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAe5F;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CASzF;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAQlG","sourcesContent":["/** Browser-safe resource claims for the opt-in dag-v2 scheduler. */\n\nimport {\n\tfindRegisteredToolClaimDefinition,\n\tisBuiltinPathClaimTool,\n\tisPlainArguments,\n\tpathClaimsOverlap,\n\tresolveBuiltinPathClaimWithIdentity,\n\tresolvePathClaimWithIdentity,\n\tresolveToolClaims,\n\tresolveToolPolicy,\n} from \"./builtin-tool-resource-claims.ts\";\nimport type { ToolParallelPolicy } from \"./parallel-tool-batch.ts\";\nimport { NEVER_PARALLEL_TOOLS } from \"./parallel-tool-batch.ts\";\nimport type { AgentTool, ResourceAccess, ResourceKeyResolver, ToolResourceClaim } from \"./types.ts\";\n\nexport { resolvePathClaimKey, resolveToolClaims } from \"./builtin-tool-resource-claims.ts\";\nexport type {\n\tResourceAccess,\n\tToolResourceAccess,\n\tToolResourceClaim,\n\tToolResourceClaims,\n\tToolResourceClaimsContext,\n} from \"./types.ts\";\n\nexport type ToolClaimResolution = { kind: \"exclusive\" } | { kind: \"claims\"; claims: ToolResourceClaim[] };\n\nexport interface ClaimableToolCall {\n\tid?: string;\n\tname: string;\n\targuments: unknown;\n}\n\nexport type RegisteredToolClaimDefinition = Pick<AgentTool, \"name\" | \"executionMode\" | \"resourceClaims\">;\n\nexport interface ResolveToolClaimsOptions {\n\tcwd: string;\n\ttoolPolicies?: ReadonlyMap<string, ToolParallelPolicy>;\n\tregisteredTools?: readonly RegisteredToolClaimDefinition[];\n\tstrictExtensionClaims?: boolean;\n\tresourceKeyResolver?: ResourceKeyResolver;\n}\n\nfunction isNonPathKind(value: unknown): value is Exclude<ToolResourceClaim[\"kind\"], \"path\"> {\n\treturn value === \"session\" || value === \"terminal\" || value === \"network\" || value === \"global\";\n}\n\nasync function normalizeCustomClaims(value: unknown, options: ResolveToolClaimsOptions): Promise<ToolClaimResolution> {\n\tif (value === \"exclusive\") return { kind: \"exclusive\" };\n\tif (!Array.isArray(value) || value.length === 0) return { kind: \"exclusive\" };\n\n\tconst claims: ToolResourceClaim[] = [];\n\tlet hasExclusiveAccess = false;\n\tfor (const candidate of value) {\n\t\tif (!isPlainArguments(candidate) || typeof candidate.key !== \"string\" || candidate.key.trim().length === 0) {\n\t\t\treturn { kind: \"exclusive\" };\n\t\t}\n\t\tif (candidate.kind === \"path\") {\n\t\t\tif (candidate.access !== \"read\" && candidate.access !== \"write\") return { kind: \"exclusive\" };\n\t\t\tconst pathResolution = await resolvePathClaimWithIdentity(candidate.key, candidate.access, options);\n\t\t\tif (pathResolution.kind === \"exclusive\") return pathResolution;\n\t\t\tclaims.push(...pathResolution.claims);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!isNonPathKind(candidate.kind)) return { kind: \"exclusive\" };\n\t\tif (candidate.access !== \"read\" && candidate.access !== \"write\" && candidate.access !== \"exclusive\") {\n\t\t\treturn { kind: \"exclusive\" };\n\t\t}\n\t\tconst access: ResourceAccess = candidate.access;\n\t\tclaims.push({ kind: candidate.kind, key: candidate.key, access });\n\t\tif (access === \"exclusive\") hasExclusiveAccess = true;\n\t}\n\treturn hasExclusiveAccess ? { kind: \"exclusive\" } : { kind: \"claims\", claims };\n}\n\n/** Resolve one call, failing malformed or rejected extension claims closed. */\nexport async function resolveToolClaimsForCall(\n\ttoolCall: ClaimableToolCall,\n\toptions: ResolveToolClaimsOptions,\n): Promise<ToolClaimResolution> {\n\tconst registeredTool = findRegisteredToolClaimDefinition(toolCall.name, options.registeredTools);\n\tif (!registeredTool?.resourceClaims) {\n\t\tif (\n\t\t\toptions.resourceKeyResolver &&\n\t\t\tisBuiltinPathClaimTool(toolCall.name) &&\n\t\t\tisPlainArguments(toolCall.arguments) &&\n\t\t\t!NEVER_PARALLEL_TOOLS.has(toolCall.name) &&\n\t\t\tresolveToolPolicy(toolCall.name, options) !== \"sequential\"\n\t\t) {\n\t\t\treturn resolveBuiltinPathClaimWithIdentity(toolCall, options);\n\t\t}\n\t\treturn resolveToolClaims(toolCall, options);\n\t}\n\tif (!isPlainArguments(toolCall.arguments)) return { kind: \"exclusive\" };\n\n\tlet resolution: ToolClaimResolution;\n\ttry {\n\t\tconst claims = await registeredTool.resourceClaims(toolCall.arguments, {\n\t\t\tcwd: options.cwd,\n\t\t\ttoolCallId: toolCall.id ?? \"\",\n\t\t});\n\t\tresolution = await normalizeCustomClaims(claims, options);\n\t} catch {\n\t\treturn { kind: \"exclusive\" };\n\t}\n\tif (\n\t\tNEVER_PARALLEL_TOOLS.has(toolCall.name) ||\n\t\ttoolCall.name === \"bash\" ||\n\t\tresolveToolPolicy(toolCall.name, options) === \"sequential\"\n\t) {\n\t\treturn { kind: \"exclusive\" };\n\t}\n\treturn resolution;\n}\n\nexport function compareClaims(left: ToolResourceClaim, right: ToolResourceClaim): number {\n\tif (left.kind !== right.kind) return left.kind < right.kind ? -1 : 1;\n\tif (left.key !== right.key) return left.key < right.key ? -1 : 1;\n\tif (left.access !== right.access) return left.access < right.access ? -1 : 1;\n\tconst leftReal = left.kind === \"path\" ? (left.realKey ?? \"\") : \"\";\n\tconst rightReal = right.kind === \"path\" ? (right.realKey ?? \"\") : \"\";\n\tif (leftReal !== rightReal) return leftReal < rightReal ? -1 : 1;\n\tconst leftInode = left.kind === \"path\" ? (left.inodeKey ?? \"\") : \"\";\n\tconst rightInode = right.kind === \"path\" ? (right.inodeKey ?? \"\") : \"\";\n\tif (leftInode !== rightInode) return leftInode < rightInode ? -1 : 1;\n\treturn 0;\n}\n\nexport function canonicalizeClaims(claims: readonly ToolResourceClaim[]): ToolResourceClaim[] {\n\treturn claims\n\t\t.map(\n\t\t\t(claim): ToolResourceClaim =>\n\t\t\t\tclaim.kind === \"path\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tkind: \"path\",\n\t\t\t\t\t\t\tkey: claim.key,\n\t\t\t\t\t\t\taccess: claim.access,\n\t\t\t\t\t\t\t...(claim.realKey === undefined ? {} : { realKey: claim.realKey }),\n\t\t\t\t\t\t\t...(claim.inodeKey === undefined ? {} : { inodeKey: claim.inodeKey }),\n\t\t\t\t\t\t}\n\t\t\t\t\t: { kind: claim.kind, key: claim.key, access: claim.access },\n\t\t)\n\t\t.sort(compareClaims);\n}\n\nexport function claimsConflict(left: ToolResourceClaim, right: ToolResourceClaim): boolean {\n\tif (left.access === \"exclusive\" || right.access === \"exclusive\") return true;\n\tif (left.kind !== right.kind) return false;\n\tif (left.kind === \"path\" && right.kind === \"path\") {\n\t\tif (!pathClaimsOverlap(left, right)) return false;\n\t} else if (left.key !== right.key) {\n\t\treturn false;\n\t}\n\treturn !(left.access === \"read\" && right.access === \"read\");\n}\n\nexport function resolutionsConflict(left: ToolClaimResolution, right: ToolClaimResolution): boolean {\n\tif (left.kind === \"exclusive\" || right.kind === \"exclusive\") return true;\n\tfor (const leftClaim of left.claims) {\n\t\tfor (const rightClaim of right.claims) {\n\t\t\tif (claimsConflict(leftClaim, rightClaim)) return true;\n\t\t}\n\t}\n\treturn false;\n}\n"]}
@@ -0,0 +1,128 @@
1
+ /** Browser-safe resource claims for the opt-in dag-v2 scheduler. */
2
+ import { findRegisteredToolClaimDefinition, isBuiltinPathClaimTool, isPlainArguments, pathClaimsOverlap, resolveBuiltinPathClaimWithIdentity, resolvePathClaimWithIdentity, resolveToolClaims, resolveToolPolicy, } from "./builtin-tool-resource-claims.js";
3
+ import { NEVER_PARALLEL_TOOLS } from "./parallel-tool-batch.js";
4
+ export { resolvePathClaimKey, resolveToolClaims } from "./builtin-tool-resource-claims.js";
5
+ function isNonPathKind(value) {
6
+ return value === "session" || value === "terminal" || value === "network" || value === "global";
7
+ }
8
+ async function normalizeCustomClaims(value, options) {
9
+ if (value === "exclusive")
10
+ return { kind: "exclusive" };
11
+ if (!Array.isArray(value) || value.length === 0)
12
+ return { kind: "exclusive" };
13
+ const claims = [];
14
+ let hasExclusiveAccess = false;
15
+ for (const candidate of value) {
16
+ if (!isPlainArguments(candidate) || typeof candidate.key !== "string" || candidate.key.trim().length === 0) {
17
+ return { kind: "exclusive" };
18
+ }
19
+ if (candidate.kind === "path") {
20
+ if (candidate.access !== "read" && candidate.access !== "write")
21
+ return { kind: "exclusive" };
22
+ const pathResolution = await resolvePathClaimWithIdentity(candidate.key, candidate.access, options);
23
+ if (pathResolution.kind === "exclusive")
24
+ return pathResolution;
25
+ claims.push(...pathResolution.claims);
26
+ continue;
27
+ }
28
+ if (!isNonPathKind(candidate.kind))
29
+ return { kind: "exclusive" };
30
+ if (candidate.access !== "read" && candidate.access !== "write" && candidate.access !== "exclusive") {
31
+ return { kind: "exclusive" };
32
+ }
33
+ const access = candidate.access;
34
+ claims.push({ kind: candidate.kind, key: candidate.key, access });
35
+ if (access === "exclusive")
36
+ hasExclusiveAccess = true;
37
+ }
38
+ return hasExclusiveAccess ? { kind: "exclusive" } : { kind: "claims", claims };
39
+ }
40
+ /** Resolve one call, failing malformed or rejected extension claims closed. */
41
+ export async function resolveToolClaimsForCall(toolCall, options) {
42
+ const registeredTool = findRegisteredToolClaimDefinition(toolCall.name, options.registeredTools);
43
+ if (!registeredTool?.resourceClaims) {
44
+ if (options.resourceKeyResolver &&
45
+ isBuiltinPathClaimTool(toolCall.name) &&
46
+ isPlainArguments(toolCall.arguments) &&
47
+ !NEVER_PARALLEL_TOOLS.has(toolCall.name) &&
48
+ resolveToolPolicy(toolCall.name, options) !== "sequential") {
49
+ return resolveBuiltinPathClaimWithIdentity(toolCall, options);
50
+ }
51
+ return resolveToolClaims(toolCall, options);
52
+ }
53
+ if (!isPlainArguments(toolCall.arguments))
54
+ return { kind: "exclusive" };
55
+ let resolution;
56
+ try {
57
+ const claims = await registeredTool.resourceClaims(toolCall.arguments, {
58
+ cwd: options.cwd,
59
+ toolCallId: toolCall.id ?? "",
60
+ });
61
+ resolution = await normalizeCustomClaims(claims, options);
62
+ }
63
+ catch {
64
+ return { kind: "exclusive" };
65
+ }
66
+ if (NEVER_PARALLEL_TOOLS.has(toolCall.name) ||
67
+ toolCall.name === "bash" ||
68
+ resolveToolPolicy(toolCall.name, options) === "sequential") {
69
+ return { kind: "exclusive" };
70
+ }
71
+ return resolution;
72
+ }
73
+ export function compareClaims(left, right) {
74
+ if (left.kind !== right.kind)
75
+ return left.kind < right.kind ? -1 : 1;
76
+ if (left.key !== right.key)
77
+ return left.key < right.key ? -1 : 1;
78
+ if (left.access !== right.access)
79
+ return left.access < right.access ? -1 : 1;
80
+ const leftReal = left.kind === "path" ? (left.realKey ?? "") : "";
81
+ const rightReal = right.kind === "path" ? (right.realKey ?? "") : "";
82
+ if (leftReal !== rightReal)
83
+ return leftReal < rightReal ? -1 : 1;
84
+ const leftInode = left.kind === "path" ? (left.inodeKey ?? "") : "";
85
+ const rightInode = right.kind === "path" ? (right.inodeKey ?? "") : "";
86
+ if (leftInode !== rightInode)
87
+ return leftInode < rightInode ? -1 : 1;
88
+ return 0;
89
+ }
90
+ export function canonicalizeClaims(claims) {
91
+ return claims
92
+ .map((claim) => claim.kind === "path"
93
+ ? {
94
+ kind: "path",
95
+ key: claim.key,
96
+ access: claim.access,
97
+ ...(claim.realKey === undefined ? {} : { realKey: claim.realKey }),
98
+ ...(claim.inodeKey === undefined ? {} : { inodeKey: claim.inodeKey }),
99
+ }
100
+ : { kind: claim.kind, key: claim.key, access: claim.access })
101
+ .sort(compareClaims);
102
+ }
103
+ export function claimsConflict(left, right) {
104
+ if (left.access === "exclusive" || right.access === "exclusive")
105
+ return true;
106
+ if (left.kind !== right.kind)
107
+ return false;
108
+ if (left.kind === "path" && right.kind === "path") {
109
+ if (!pathClaimsOverlap(left, right))
110
+ return false;
111
+ }
112
+ else if (left.key !== right.key) {
113
+ return false;
114
+ }
115
+ return !(left.access === "read" && right.access === "read");
116
+ }
117
+ export function resolutionsConflict(left, right) {
118
+ if (left.kind === "exclusive" || right.kind === "exclusive")
119
+ return true;
120
+ for (const leftClaim of left.claims) {
121
+ for (const rightClaim of right.claims) {
122
+ if (claimsConflict(leftClaim, rightClaim))
123
+ return true;
124
+ }
125
+ }
126
+ return false;
127
+ }
128
+ //# sourceMappingURL=tool-resource-claims.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-resource-claims.js","sourceRoot":"","sources":["../src/tool-resource-claims.ts"],"names":[],"mappings":"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","sourcesContent":["/** Browser-safe resource claims for the opt-in dag-v2 scheduler. */\n\nimport {\n\tfindRegisteredToolClaimDefinition,\n\tisBuiltinPathClaimTool,\n\tisPlainArguments,\n\tpathClaimsOverlap,\n\tresolveBuiltinPathClaimWithIdentity,\n\tresolvePathClaimWithIdentity,\n\tresolveToolClaims,\n\tresolveToolPolicy,\n} from \"./builtin-tool-resource-claims.ts\";\nimport type { ToolParallelPolicy } from \"./parallel-tool-batch.ts\";\nimport { NEVER_PARALLEL_TOOLS } from \"./parallel-tool-batch.ts\";\nimport type { AgentTool, ResourceAccess, ResourceKeyResolver, ToolResourceClaim } from \"./types.ts\";\n\nexport { resolvePathClaimKey, resolveToolClaims } from \"./builtin-tool-resource-claims.ts\";\nexport type {\n\tResourceAccess,\n\tToolResourceAccess,\n\tToolResourceClaim,\n\tToolResourceClaims,\n\tToolResourceClaimsContext,\n} from \"./types.ts\";\n\nexport type ToolClaimResolution = { kind: \"exclusive\" } | { kind: \"claims\"; claims: ToolResourceClaim[] };\n\nexport interface ClaimableToolCall {\n\tid?: string;\n\tname: string;\n\targuments: unknown;\n}\n\nexport type RegisteredToolClaimDefinition = Pick<AgentTool, \"name\" | \"executionMode\" | \"resourceClaims\">;\n\nexport interface ResolveToolClaimsOptions {\n\tcwd: string;\n\ttoolPolicies?: ReadonlyMap<string, ToolParallelPolicy>;\n\tregisteredTools?: readonly RegisteredToolClaimDefinition[];\n\tstrictExtensionClaims?: boolean;\n\tresourceKeyResolver?: ResourceKeyResolver;\n}\n\nfunction isNonPathKind(value: unknown): value is Exclude<ToolResourceClaim[\"kind\"], \"path\"> {\n\treturn value === \"session\" || value === \"terminal\" || value === \"network\" || value === \"global\";\n}\n\nasync function normalizeCustomClaims(value: unknown, options: ResolveToolClaimsOptions): Promise<ToolClaimResolution> {\n\tif (value === \"exclusive\") return { kind: \"exclusive\" };\n\tif (!Array.isArray(value) || value.length === 0) return { kind: \"exclusive\" };\n\n\tconst claims: ToolResourceClaim[] = [];\n\tlet hasExclusiveAccess = false;\n\tfor (const candidate of value) {\n\t\tif (!isPlainArguments(candidate) || typeof candidate.key !== \"string\" || candidate.key.trim().length === 0) {\n\t\t\treturn { kind: \"exclusive\" };\n\t\t}\n\t\tif (candidate.kind === \"path\") {\n\t\t\tif (candidate.access !== \"read\" && candidate.access !== \"write\") return { kind: \"exclusive\" };\n\t\t\tconst pathResolution = await resolvePathClaimWithIdentity(candidate.key, candidate.access, options);\n\t\t\tif (pathResolution.kind === \"exclusive\") return pathResolution;\n\t\t\tclaims.push(...pathResolution.claims);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!isNonPathKind(candidate.kind)) return { kind: \"exclusive\" };\n\t\tif (candidate.access !== \"read\" && candidate.access !== \"write\" && candidate.access !== \"exclusive\") {\n\t\t\treturn { kind: \"exclusive\" };\n\t\t}\n\t\tconst access: ResourceAccess = candidate.access;\n\t\tclaims.push({ kind: candidate.kind, key: candidate.key, access });\n\t\tif (access === \"exclusive\") hasExclusiveAccess = true;\n\t}\n\treturn hasExclusiveAccess ? { kind: \"exclusive\" } : { kind: \"claims\", claims };\n}\n\n/** Resolve one call, failing malformed or rejected extension claims closed. */\nexport async function resolveToolClaimsForCall(\n\ttoolCall: ClaimableToolCall,\n\toptions: ResolveToolClaimsOptions,\n): Promise<ToolClaimResolution> {\n\tconst registeredTool = findRegisteredToolClaimDefinition(toolCall.name, options.registeredTools);\n\tif (!registeredTool?.resourceClaims) {\n\t\tif (\n\t\t\toptions.resourceKeyResolver &&\n\t\t\tisBuiltinPathClaimTool(toolCall.name) &&\n\t\t\tisPlainArguments(toolCall.arguments) &&\n\t\t\t!NEVER_PARALLEL_TOOLS.has(toolCall.name) &&\n\t\t\tresolveToolPolicy(toolCall.name, options) !== \"sequential\"\n\t\t) {\n\t\t\treturn resolveBuiltinPathClaimWithIdentity(toolCall, options);\n\t\t}\n\t\treturn resolveToolClaims(toolCall, options);\n\t}\n\tif (!isPlainArguments(toolCall.arguments)) return { kind: \"exclusive\" };\n\n\tlet resolution: ToolClaimResolution;\n\ttry {\n\t\tconst claims = await registeredTool.resourceClaims(toolCall.arguments, {\n\t\t\tcwd: options.cwd,\n\t\t\ttoolCallId: toolCall.id ?? \"\",\n\t\t});\n\t\tresolution = await normalizeCustomClaims(claims, options);\n\t} catch {\n\t\treturn { kind: \"exclusive\" };\n\t}\n\tif (\n\t\tNEVER_PARALLEL_TOOLS.has(toolCall.name) ||\n\t\ttoolCall.name === \"bash\" ||\n\t\tresolveToolPolicy(toolCall.name, options) === \"sequential\"\n\t) {\n\t\treturn { kind: \"exclusive\" };\n\t}\n\treturn resolution;\n}\n\nexport function compareClaims(left: ToolResourceClaim, right: ToolResourceClaim): number {\n\tif (left.kind !== right.kind) return left.kind < right.kind ? -1 : 1;\n\tif (left.key !== right.key) return left.key < right.key ? -1 : 1;\n\tif (left.access !== right.access) return left.access < right.access ? -1 : 1;\n\tconst leftReal = left.kind === \"path\" ? (left.realKey ?? \"\") : \"\";\n\tconst rightReal = right.kind === \"path\" ? (right.realKey ?? \"\") : \"\";\n\tif (leftReal !== rightReal) return leftReal < rightReal ? -1 : 1;\n\tconst leftInode = left.kind === \"path\" ? (left.inodeKey ?? \"\") : \"\";\n\tconst rightInode = right.kind === \"path\" ? (right.inodeKey ?? \"\") : \"\";\n\tif (leftInode !== rightInode) return leftInode < rightInode ? -1 : 1;\n\treturn 0;\n}\n\nexport function canonicalizeClaims(claims: readonly ToolResourceClaim[]): ToolResourceClaim[] {\n\treturn claims\n\t\t.map(\n\t\t\t(claim): ToolResourceClaim =>\n\t\t\t\tclaim.kind === \"path\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tkind: \"path\",\n\t\t\t\t\t\t\tkey: claim.key,\n\t\t\t\t\t\t\taccess: claim.access,\n\t\t\t\t\t\t\t...(claim.realKey === undefined ? {} : { realKey: claim.realKey }),\n\t\t\t\t\t\t\t...(claim.inodeKey === undefined ? {} : { inodeKey: claim.inodeKey }),\n\t\t\t\t\t\t}\n\t\t\t\t\t: { kind: claim.kind, key: claim.key, access: claim.access },\n\t\t)\n\t\t.sort(compareClaims);\n}\n\nexport function claimsConflict(left: ToolResourceClaim, right: ToolResourceClaim): boolean {\n\tif (left.access === \"exclusive\" || right.access === \"exclusive\") return true;\n\tif (left.kind !== right.kind) return false;\n\tif (left.kind === \"path\" && right.kind === \"path\") {\n\t\tif (!pathClaimsOverlap(left, right)) return false;\n\t} else if (left.key !== right.key) {\n\t\treturn false;\n\t}\n\treturn !(left.access === \"read\" && right.access === \"read\");\n}\n\nexport function resolutionsConflict(left: ToolClaimResolution, right: ToolClaimResolution): boolean {\n\tif (left.kind === \"exclusive\" || right.kind === \"exclusive\") return true;\n\tfor (const leftClaim of left.claims) {\n\t\tfor (const rightClaim of right.claims) {\n\t\t\tif (claimsConflict(leftClaim, rightClaim)) return true;\n\t\t}\n\t}\n\treturn false;\n}\n"]}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Per-tool execution timeout and cancellation for the agent loop.
3
+ *
4
+ * The agent loop awaits each tool's `execute` promise at a single shared
5
+ * chokepoint. A tool that ignores its `AbortSignal` and never settles would
6
+ * otherwise stall the whole run. This module bounds that risk without process
7
+ * killing: the real execute promise is raced against two terminal causes — a
8
+ * per-call timeout timer and the parent run's abort — so an uncooperative tool
9
+ * still yields an immediate, immutable terminal result.
10
+ *
11
+ * The child `AbortSignal` handed to the tool is best-effort cooperative
12
+ * cancellation only; correctness comes from the race, not from the tool
13
+ * honoring the signal. `AbortSignal.any()` is intentionally not used: the
14
+ * parent-abort and timeout wiring is explicit so timer/listener disposal is
15
+ * idempotent and runs on every outcome.
16
+ *
17
+ * A late settlement of the real promise (after the terminal cause already won)
18
+ * is observed exactly once for audit only. It never emits a second tool result
19
+ * or lifecycle end, never mutates the committed result, and — because the real
20
+ * promise is wrapped so it never rejects — can never surface as an unhandled
21
+ * rejection.
22
+ */
23
+ import type { AgentLoopConfig, AgentTool, AgentToolResult, AgentToolUpdateCallback, ToolExecutionPolicy, ToolLateSettlementOutcome, ToolTimeoutDisposition } from "./types.ts";
24
+ export type { ToolDispositionEnvelope } from "./tool-execution-boundary.ts";
25
+ export { createAbortedToolResult, createTimeoutToolResult } from "./tool-execution-boundary.ts";
26
+ /**
27
+ * Resolve the effective {@link ToolExecutionPolicy} with the release default
28
+ * `lateSettlement: "audit"`: late settlements are observable audit events
29
+ * unless a caller explicitly opts out with `"ignore"`.
30
+ */
31
+ export declare function resolveToolExecutionPolicy(policy: Partial<ToolExecutionPolicy> | undefined): ToolExecutionPolicy;
32
+ /** Audit-only description of a real tool promise settling after its terminal cause won. */
33
+ export interface ToolLateSettlement {
34
+ toolCallId: string;
35
+ toolName: string;
36
+ /** The terminal disposition that was already committed when the real promise settled. */
37
+ disposition: ToolTimeoutDisposition;
38
+ /** How the real tool promise eventually settled. Disposition-safe metadata only. */
39
+ outcome: ToolLateSettlementOutcome;
40
+ }
41
+ /**
42
+ * Resolve the effective per-call timeout with strict precedence:
43
+ * per-tool `AgentTool.timeoutMs` > per-name `config.toolTimeouts[name]` >
44
+ * global `config.toolTimeoutMs`.
45
+ *
46
+ * The first level that is *present* (not `undefined`) wins, so a per-tool `0`
47
+ * deliberately disables the timeout even when a global default is set. A
48
+ * resolved value that is absent, non-finite, or non-positive returns `0`, which
49
+ * disables only the timer. Parent cancellation is still raced so an
50
+ * uncooperative tool cannot keep an aborted run open.
51
+ */
52
+ export declare function resolveToolTimeoutMs(tool: Pick<AgentTool<any>, "timeoutMs">, config: Pick<AgentLoopConfig, "toolTimeoutMs" | "toolTimeouts">, toolName: string): number;
53
+ export interface RunToolCallWithTimeoutOptions<TDetails = any> {
54
+ toolCallId: string;
55
+ toolName: string;
56
+ /** Effective timeout in ms. A non-positive value disables the timer, not parent cancellation. */
57
+ timeoutMs: number;
58
+ /** Parent run abort signal, if any. */
59
+ signal: AbortSignal | undefined;
60
+ /** Start the real tool, passing the child (best-effort) signal and update sink. */
61
+ start: (childSignal: AbortSignal, onUpdate: AgentToolUpdateCallback<TDetails>) => Promise<AgentToolResult<TDetails>>;
62
+ /** Emit a `tool_execution_update` for a partial result observed before terminality. */
63
+ emitUpdate: (partialResult: AgentToolResult<TDetails>) => Promise<void> | void;
64
+ /** Emit the audit-only late-settlement event exactly once, after the terminal cause won. */
65
+ emitLateSettlement: (settlement: ToolLateSettlement) => Promise<void> | void;
66
+ /** Map a thrown/rejected tool error to a normal error result (real completion path). */
67
+ toErrorResult: (error: unknown) => AgentToolResult<any>;
68
+ /**
69
+ * Late-settlement policy (default `"audit"`). `"ignore"` drops the audit
70
+ * event; the committed terminal result is immutable under both policies.
71
+ */
72
+ lateSettlement?: ToolExecutionPolicy["lateSettlement"];
73
+ }
74
+ export interface RunToolCallWithTimeoutResult {
75
+ result: AgentToolResult<any>;
76
+ isError: boolean;
77
+ /** True when the tool's `execute` actually began before the result was committed. */
78
+ executionStarted: boolean;
79
+ /** Terminal cause when the runtime (not the tool) committed the result. */
80
+ terminalDisposition?: ToolTimeoutDisposition;
81
+ }
82
+ /**
83
+ * Race a tool's `execute` promise against a per-call timeout and parent abort.
84
+ *
85
+ * Control flow (single path, no `AbortSignal.any()`):
86
+ * - A timer and a parent-abort listener each resolve one shared "terminal cause"
87
+ * deferred. Whichever fires first wins; resolving is idempotent.
88
+ * - The timer callback resolves the timeout cause *before* aborting the child so
89
+ * a tool that rejects promptly on abort cannot make "aborted" win a timeout.
90
+ * - `Promise.race` picks the real settlement or the terminal cause. Timer and
91
+ * listener disposal is idempotent and runs on every outcome.
92
+ * - On a terminal-cause win, the real promise (wrapped so it never rejects) is
93
+ * observed once for the audit event and the committed result is immutable.
94
+ */
95
+ export declare function runToolCallWithTimeout<TDetails = any>(options: RunToolCallWithTimeoutOptions<TDetails>): Promise<RunToolCallWithTimeoutResult>;
96
+ //# sourceMappingURL=tool-timeout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-timeout.d.ts","sourceRoot":"","sources":["../src/tool-timeout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EACX,eAAe,EACf,SAAS,EACT,eAAe,EACf,uBAAuB,EACvB,mBAAmB,EACnB,yBAAyB,EACzB,sBAAsB,EACtB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEhG;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,SAAS,GAAG,mBAAmB,CAMhH;AAED,2FAA2F;AAC3F,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,WAAW,EAAE,sBAAsB,CAAC;IACpC,oFAAoF;IACpF,OAAO,EAAE,yBAAyB,CAAC;CACnC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CACnC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC,EACvC,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,eAAe,GAAG,cAAc,CAAC,EAC/D,QAAQ,EAAE,MAAM,GACd,MAAM,CAaR;AAQD,MAAM,WAAW,6BAA6B,CAAC,QAAQ,GAAG,GAAG;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,iGAAiG;IACjG,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,mFAAmF;IACnF,KAAK,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,uBAAuB,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrH,uFAAuF;IACvF,UAAU,EAAE,CAAC,aAAa,EAAE,eAAe,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/E,4FAA4F;IAC5F,kBAAkB,EAAE,CAAC,UAAU,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7E,wFAAwF;IACxF,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,eAAe,CAAC,GAAG,CAAC,CAAC;IACxD;;;OAGG;IACH,cAAc,CAAC,EAAE,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,4BAA4B;IAC5C,MAAM,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,qFAAqF;IACrF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,mBAAmB,CAAC,EAAE,sBAAsB,CAAC;CAC7C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CAAC,QAAQ,GAAG,GAAG,EAC1D,OAAO,EAAE,6BAA6B,CAAC,QAAQ,CAAC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CAsHvC","sourcesContent":["/**\n * Per-tool execution timeout and cancellation for the agent loop.\n *\n * The agent loop awaits each tool's `execute` promise at a single shared\n * chokepoint. A tool that ignores its `AbortSignal` and never settles would\n * otherwise stall the whole run. This module bounds that risk without process\n * killing: the real execute promise is raced against two terminal causes — a\n * per-call timeout timer and the parent run's abort — so an uncooperative tool\n * still yields an immediate, immutable terminal result.\n *\n * The child `AbortSignal` handed to the tool is best-effort cooperative\n * cancellation only; correctness comes from the race, not from the tool\n * honoring the signal. `AbortSignal.any()` is intentionally not used: the\n * parent-abort and timeout wiring is explicit so timer/listener disposal is\n * idempotent and runs on every outcome.\n *\n * A late settlement of the real promise (after the terminal cause already won)\n * is observed exactly once for audit only. It never emits a second tool result\n * or lifecycle end, never mutates the committed result, and — because the real\n * promise is wrapped so it never rejects — can never surface as an unhandled\n * rejection.\n */\n\nimport { createAbortedToolResult, createTimeoutToolResult } from \"./tool-execution-boundary.ts\";\nimport type {\n\tAgentLoopConfig,\n\tAgentTool,\n\tAgentToolResult,\n\tAgentToolUpdateCallback,\n\tToolExecutionPolicy,\n\tToolLateSettlementOutcome,\n\tToolTimeoutDisposition,\n} from \"./types.ts\";\n\nexport type { ToolDispositionEnvelope } from \"./tool-execution-boundary.ts\";\nexport { createAbortedToolResult, createTimeoutToolResult } from \"./tool-execution-boundary.ts\";\n\n/**\n * Resolve the effective {@link ToolExecutionPolicy} with the release default\n * `lateSettlement: \"audit\"`: late settlements are observable audit events\n * unless a caller explicitly opts out with `\"ignore\"`.\n */\nexport function resolveToolExecutionPolicy(policy: Partial<ToolExecutionPolicy> | undefined): ToolExecutionPolicy {\n\treturn {\n\t\t...(policy?.timeoutMs === undefined ? {} : { timeoutMs: policy.timeoutMs }),\n\t\t...(policy?.cancelSiblingsOnFatal === undefined ? {} : { cancelSiblingsOnFatal: policy.cancelSiblingsOnFatal }),\n\t\tlateSettlement: policy?.lateSettlement === \"ignore\" ? \"ignore\" : \"audit\",\n\t};\n}\n\n/** Audit-only description of a real tool promise settling after its terminal cause won. */\nexport interface ToolLateSettlement {\n\ttoolCallId: string;\n\ttoolName: string;\n\t/** The terminal disposition that was already committed when the real promise settled. */\n\tdisposition: ToolTimeoutDisposition;\n\t/** How the real tool promise eventually settled. Disposition-safe metadata only. */\n\toutcome: ToolLateSettlementOutcome;\n}\n\n/**\n * Resolve the effective per-call timeout with strict precedence:\n * per-tool `AgentTool.timeoutMs` > per-name `config.toolTimeouts[name]` >\n * global `config.toolTimeoutMs`.\n *\n * The first level that is *present* (not `undefined`) wins, so a per-tool `0`\n * deliberately disables the timeout even when a global default is set. A\n * resolved value that is absent, non-finite, or non-positive returns `0`, which\n * disables only the timer. Parent cancellation is still raced so an\n * uncooperative tool cannot keep an aborted run open.\n */\nexport function resolveToolTimeoutMs(\n\ttool: Pick<AgentTool<any>, \"timeoutMs\">,\n\tconfig: Pick<AgentLoopConfig, \"toolTimeoutMs\" | \"toolTimeouts\">,\n\ttoolName: string,\n): number {\n\tlet chosen: number | undefined;\n\tif (tool.timeoutMs !== undefined) {\n\t\tchosen = tool.timeoutMs;\n\t} else if (config.toolTimeouts?.[toolName] !== undefined) {\n\t\tchosen = config.toolTimeouts[toolName];\n\t} else {\n\t\tchosen = config.toolTimeoutMs;\n\t}\n\tif (typeof chosen !== \"number\" || !Number.isFinite(chosen) || chosen <= 0) {\n\t\treturn 0;\n\t}\n\treturn chosen;\n}\n\ntype TerminalCause = { kind: ToolTimeoutDisposition };\n\ntype RealSettlement<TDetails> =\n\t| { kind: \"resolved\"; result: AgentToolResult<TDetails> }\n\t| { kind: \"rejected\"; error: unknown };\n\nexport interface RunToolCallWithTimeoutOptions<TDetails = any> {\n\ttoolCallId: string;\n\ttoolName: string;\n\t/** Effective timeout in ms. A non-positive value disables the timer, not parent cancellation. */\n\ttimeoutMs: number;\n\t/** Parent run abort signal, if any. */\n\tsignal: AbortSignal | undefined;\n\t/** Start the real tool, passing the child (best-effort) signal and update sink. */\n\tstart: (childSignal: AbortSignal, onUpdate: AgentToolUpdateCallback<TDetails>) => Promise<AgentToolResult<TDetails>>;\n\t/** Emit a `tool_execution_update` for a partial result observed before terminality. */\n\temitUpdate: (partialResult: AgentToolResult<TDetails>) => Promise<void> | void;\n\t/** Emit the audit-only late-settlement event exactly once, after the terminal cause won. */\n\temitLateSettlement: (settlement: ToolLateSettlement) => Promise<void> | void;\n\t/** Map a thrown/rejected tool error to a normal error result (real completion path). */\n\ttoErrorResult: (error: unknown) => AgentToolResult<any>;\n\t/**\n\t * Late-settlement policy (default `\"audit\"`). `\"ignore\"` drops the audit\n\t * event; the committed terminal result is immutable under both policies.\n\t */\n\tlateSettlement?: ToolExecutionPolicy[\"lateSettlement\"];\n}\n\nexport interface RunToolCallWithTimeoutResult {\n\tresult: AgentToolResult<any>;\n\tisError: boolean;\n\t/** True when the tool's `execute` actually began before the result was committed. */\n\texecutionStarted: boolean;\n\t/** Terminal cause when the runtime (not the tool) committed the result. */\n\tterminalDisposition?: ToolTimeoutDisposition;\n}\n\n/**\n * Race a tool's `execute` promise against a per-call timeout and parent abort.\n *\n * Control flow (single path, no `AbortSignal.any()`):\n * - A timer and a parent-abort listener each resolve one shared \"terminal cause\"\n * deferred. Whichever fires first wins; resolving is idempotent.\n * - The timer callback resolves the timeout cause *before* aborting the child so\n * a tool that rejects promptly on abort cannot make \"aborted\" win a timeout.\n * - `Promise.race` picks the real settlement or the terminal cause. Timer and\n * listener disposal is idempotent and runs on every outcome.\n * - On a terminal-cause win, the real promise (wrapped so it never rejects) is\n * observed once for the audit event and the committed result is immutable.\n */\nexport async function runToolCallWithTimeout<TDetails = any>(\n\toptions: RunToolCallWithTimeoutOptions<TDetails>,\n): Promise<RunToolCallWithTimeoutResult> {\n\tconst { toolCallId, toolName, timeoutMs, signal, start, emitUpdate, emitLateSettlement, toErrorResult } = options;\n\tconst lateSettlementPolicy = resolveToolExecutionPolicy({ lateSettlement: options.lateSettlement }).lateSettlement;\n\n\t// Defensive: a parent already aborted before execution starts never runs the\n\t// tool. prepareToolCall normally catches this earlier.\n\tif (signal?.aborted) {\n\t\treturn {\n\t\t\tresult: createAbortedToolResult(false),\n\t\t\tisError: true,\n\t\t\texecutionStarted: false,\n\t\t\tterminalDisposition: \"aborted\",\n\t\t};\n\t}\n\n\tconst childController = new AbortController();\n\tlet raceSettled = false;\n\tlet disposed = false;\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\n\tlet resolveCause!: (cause: TerminalCause) => void;\n\tconst causePromise = new Promise<TerminalCause>((resolve) => {\n\t\tresolveCause = resolve;\n\t});\n\n\tconst abortChild = (reason: unknown): void => {\n\t\tif (!childController.signal.aborted) {\n\t\t\tchildController.abort(reason);\n\t\t}\n\t};\n\n\tconst onParentAbort = (): void => {\n\t\t// Resolve the terminal cause before aborting the child so the committed\n\t\t// disposition stays \"aborted\" even for a tool that rejects on its signal.\n\t\tresolveCause({ kind: \"aborted\" });\n\t\tabortChild(signal?.reason);\n\t};\n\n\tconst dispose = (): void => {\n\t\tif (disposed) {\n\t\t\treturn;\n\t\t}\n\t\tdisposed = true;\n\t\tif (timer !== undefined) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t}\n\t\tsignal?.removeEventListener(\"abort\", onParentAbort);\n\t};\n\n\tif (timeoutMs > 0) {\n\t\ttimer = setTimeout(() => {\n\t\t\t// Reason ordering: settle the timeout cause first, then signal the child.\n\t\t\tresolveCause({ kind: \"timeout\" });\n\t\t\tabortChild(new Error(`Tool \"${toolName}\" timed out after ${timeoutMs}ms`));\n\t\t}, timeoutMs);\n\t}\n\n\tsignal?.addEventListener(\"abort\", onParentAbort, { once: true });\n\n\t// Updates are observation-only: deliver those seen before terminality, but\n\t// never let listener settlement or failure delay the terminal race.\n\tconst gatedUpdate: AgentToolUpdateCallback<TDetails> = (partialResult) => {\n\t\tif (raceSettled) return;\n\t\tvoid Promise.resolve()\n\t\t\t.then(() => emitUpdate(partialResult))\n\t\t\t.catch(() => undefined);\n\t};\n\n\t// Wrap so a synchronous throw from `start` becomes a rejection and, crucially,\n\t// so this promise never rejects — both settlements map to a value. That makes\n\t// the late observer safe from unhandled rejections.\n\tconst realSettled: Promise<RealSettlement<TDetails>> = (async () =>\n\t\tstart(childController.signal, gatedUpdate))().then(\n\t\t(result) => ({ kind: \"resolved\" as const, result }),\n\t\t(error) => ({ kind: \"rejected\" as const, error }),\n\t);\n\n\tconst raced = await Promise.race<\n\t\t{ from: \"real\"; settlement: RealSettlement<TDetails> } | { from: \"cause\"; cause: TerminalCause }\n\t>([\n\t\trealSettled.then((settlement) => ({ from: \"real\" as const, settlement })),\n\t\tcausePromise.then((cause) => ({ from: \"cause\" as const, cause })),\n\t]);\n\n\traceSettled = true;\n\tdispose();\n\n\tif (raced.from === \"real\") {\n\t\t// The tool settled first: behave exactly like the no-timeout path.\n\t\tconst settlement = raced.settlement;\n\t\tif (settlement.kind === \"resolved\") {\n\t\t\treturn { result: settlement.result, isError: false, executionStarted: true };\n\t\t}\n\t\treturn { result: toErrorResult(settlement.error), isError: true, executionStarted: true };\n\t}\n\n\t// A terminal cause won. Observe the real promise's eventual settlement exactly\n\t// once for audit only; realSettled never rejects and the chained catch guards\n\t// against a throwing emit, so no unhandled rejection is possible. The\n\t// `\"ignore\"` policy drops the audit event but never the immutable result.\n\tconst disposition = raced.cause.kind;\n\tif (lateSettlementPolicy === \"audit\") {\n\t\trealSettled\n\t\t\t.then((settlement) =>\n\t\t\t\temitLateSettlement({\n\t\t\t\t\ttoolCallId,\n\t\t\t\t\ttoolName,\n\t\t\t\t\tdisposition,\n\t\t\t\t\toutcome: settlement.kind === \"resolved\" ? \"resolved\" : \"rejected\",\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.catch(() => {});\n\t}\n\n\tconst result =\n\t\tdisposition === \"timeout\" ? createTimeoutToolResult(toolName, timeoutMs) : createAbortedToolResult(true);\n\treturn { result, isError: true, executionStarted: true, terminalDisposition: disposition };\n}\n"]}