omk-agent-core 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -1
- package/dist/harness/agent-harness.d.ts.map +1 -1
- package/dist/harness/agent-harness.js +2 -1
- package/dist/harness/agent-harness.js.map +1 -1
- package/dist/harness/compaction/compaction.d.ts.map +1 -1
- package/dist/harness/compaction/compaction.js +4 -4
- package/dist/harness/compaction/compaction.js.map +1 -1
- package/dist/harness/compaction/summary-fallback.d.ts +12 -0
- package/dist/harness/compaction/summary-fallback.d.ts.map +1 -0
- package/dist/harness/compaction/summary-fallback.js +52 -0
- package/dist/harness/compaction/summary-fallback.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tool-execution-boundary.d.ts +1 -1
- package/dist/tool-execution-boundary.d.ts.map +1 -1
- package/dist/tool-execution-boundary.js +17 -7
- package/dist/tool-execution-boundary.js.map +1 -1
- package/dist/tool-timeout.d.ts.map +1 -1
- package/dist/tool-timeout.js +6 -1
- package/dist/tool-timeout.js.map +1 -1
- package/package.json +5 -5
- package/dist/tool-dag-read-aware.d.ts +0 -11
- package/dist/tool-dag-read-aware.d.ts.map +0 -1
- package/dist/tool-dag-read-aware.js +0 -34
- package/dist/tool-dag-read-aware.js.map +0 -1
|
@@ -1 +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: [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: `Tool \"${toolName}\" timed out after ${timeoutMs}ms. Cancellation was requested; execution may still be running.`,\n\t\t\t},\n\t\t],\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"]}
|
|
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 { truncateTail } from \"./harness/utils/truncate.ts\";\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(\n\ttoolName: string,\n\ttimeoutMs: number,\n\tpartial?: AgentToolResult<unknown>,\n): AgentToolResult<ToolDispositionEnvelope> {\n\tconst notice = `Tool \"${toolName}\" timed out after ${timeoutMs}ms. Cancellation was requested; execution may still be running.`;\n\treturn createImmutableSnapshot({\n\t\tcontent: [{ type: \"text\", text: withPartialOutput(notice, partial) }],\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/**\n * Spec 076 #1: keep the text a tool streamed before the timeout won, ahead of the\n * notice. Only text blocks are kept, capped by the shared tail limits (2000 lines /\n * 50 KB, the same as bash and spec 066). Without streamed text the notice is unchanged.\n */\nfunction withPartialOutput(notice: string, partial: AgentToolResult<unknown> | undefined): string {\n\tconst streamed = (partial?.content ?? []).map((block) => (block.type === \"text\" ? block.text : \"\")).join(\"\");\n\tif (!streamed) return notice;\n\tconst details = partial?.details as { fullOutputPath?: unknown } | undefined;\n\tconst fullOutput = typeof details?.fullOutputPath === \"string\" ? `\\nFull output: ${details.fullOutputPath}` : \"\";\n\treturn `${truncateTail(streamed).content}\\n\\n${notice}${fullOutput}`;\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"]}
|
|
@@ -1 +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"]}
|
|
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,CA2HvC","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\t// Last partial result streamed before terminality; the timeout result keeps its text (spec 076).\n\tlet lastPartial: AgentToolResult<TDetails> | 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\tlastPartial = partialResult;\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\"\n\t\t\t? createTimeoutToolResult(toolName, timeoutMs, lastPartial)\n\t\t\t: createAbortedToolResult(true);\n\treturn { result, isError: true, executionStarted: true, terminalDisposition: disposition };\n}\n"]}
|
package/dist/tool-timeout.js
CHANGED
|
@@ -91,6 +91,8 @@ export async function runToolCallWithTimeout(options) {
|
|
|
91
91
|
let raceSettled = false;
|
|
92
92
|
let disposed = false;
|
|
93
93
|
let timer;
|
|
94
|
+
// Last partial result streamed before terminality; the timeout result keeps its text (spec 076).
|
|
95
|
+
let lastPartial;
|
|
94
96
|
let resolveCause;
|
|
95
97
|
const causePromise = new Promise((resolve) => {
|
|
96
98
|
resolveCause = resolve;
|
|
@@ -130,6 +132,7 @@ export async function runToolCallWithTimeout(options) {
|
|
|
130
132
|
const gatedUpdate = (partialResult) => {
|
|
131
133
|
if (raceSettled)
|
|
132
134
|
return;
|
|
135
|
+
lastPartial = partialResult;
|
|
133
136
|
void Promise.resolve()
|
|
134
137
|
.then(() => emitUpdate(partialResult))
|
|
135
138
|
.catch(() => undefined);
|
|
@@ -167,7 +170,9 @@ export async function runToolCallWithTimeout(options) {
|
|
|
167
170
|
}))
|
|
168
171
|
.catch(() => { });
|
|
169
172
|
}
|
|
170
|
-
const result = disposition === "timeout"
|
|
173
|
+
const result = disposition === "timeout"
|
|
174
|
+
? createTimeoutToolResult(toolName, timeoutMs, lastPartial)
|
|
175
|
+
: createAbortedToolResult(true);
|
|
171
176
|
return { result, isError: true, executionStarted: true, terminalDisposition: disposition };
|
|
172
177
|
}
|
|
173
178
|
//# sourceMappingURL=tool-timeout.js.map
|
package/dist/tool-timeout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-timeout.js","sourceRoot":"","sources":["../src/tool-timeout.ts"],"names":[],"mappings":"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","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"]}
|
|
1
|
+
{"version":3,"file":"tool-timeout.js","sourceRoot":"","sources":["../src/tool-timeout.ts"],"names":[],"mappings":"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","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\t// Last partial result streamed before terminality; the timeout result keeps its text (spec 076).\n\tlet lastPartial: AgentToolResult<TDetails> | 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\tlastPartial = partialResult;\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\"\n\t\t\t? createTimeoutToolResult(toolName, timeoutMs, lastPartial)\n\t\t\t: createAbortedToolResult(true);\n\treturn { result, isError: true, executionStarted: true, terminalDisposition: disposition };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omk-agent-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"CHANGELOG.md"
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
|
-
"clean": "
|
|
25
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
26
26
|
"build": "tsgo -p tsconfig.build.json",
|
|
27
27
|
"test": "vitest --run",
|
|
28
28
|
"test:harness": "vitest --run --config vitest.harness.config.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"prepublishOnly": "npm run clean && npm run build"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"omk-ai": "^1.
|
|
33
|
+
"omk-ai": "^1.5.0",
|
|
34
34
|
"ignore": "7.0.6",
|
|
35
35
|
"typebox": "1.3.11",
|
|
36
36
|
"yaml": "2.9.0"
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/node": "24.12.4",
|
|
57
|
-
"@vitest/coverage-v8": "
|
|
57
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
58
58
|
"fast-check": "4.9.0",
|
|
59
59
|
"typescript": "5.9.3",
|
|
60
|
-
"vitest": "
|
|
60
|
+
"vitest": "4.1.11"
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Source-directed dependency scan with the read/read no-conflict invariant.
|
|
3
|
-
* The existing conflict predicate stays authoritative for paths, aliases,
|
|
4
|
-
* exclusive barriers, and custom resource kinds.
|
|
5
|
-
*
|
|
6
|
-
* O(n + n*w + E) time, O(n + E) memory, where w is the number of entries
|
|
7
|
-
* not proved read-only and E is the output edge count. Worst case stays O(n²).
|
|
8
|
-
* `isReadOnly` must mean that any two entries it accepts cannot conflict.
|
|
9
|
-
*/
|
|
10
|
-
export declare function buildReadAwareDependencies<T>(entries: readonly T[], isReadOnly: (entry: T) => boolean, conflicts: (earlier: T, current: T) => boolean): number[][];
|
|
11
|
-
//# sourceMappingURL=tool-dag-read-aware.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tool-dag-read-aware.d.ts","sourceRoot":"","sources":["../src/tool-dag-read-aware.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAC3C,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,EACjC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,OAAO,GAC5C,MAAM,EAAE,EAAE,CAoBZ","sourcesContent":["/**\n * Source-directed dependency scan with the read/read no-conflict invariant.\n * The existing conflict predicate stays authoritative for paths, aliases,\n * exclusive barriers, and custom resource kinds.\n *\n * O(n + n*w + E) time, O(n + E) memory, where w is the number of entries\n * not proved read-only and E is the output edge count. Worst case stays O(n²).\n * `isReadOnly` must mean that any two entries it accepts cannot conflict.\n */\nexport function buildReadAwareDependencies<T>(\n\tentries: readonly T[],\n\tisReadOnly: (entry: T) => boolean,\n\tconflicts: (earlier: T, current: T) => boolean,\n): number[][] {\n\tconst priorWriters: number[] = [];\n\tconst dependencies: number[][] = [];\n\tfor (let index = 0; index < entries.length; index++) {\n\t\tconst current = entries[index];\n\t\tconst readOnly = isReadOnly(current);\n\t\tconst blockers: number[] = [];\n\t\tif (readOnly) {\n\t\t\tfor (const earlier of priorWriters) {\n\t\t\t\tif (conflicts(entries[earlier], current)) blockers.push(earlier);\n\t\t\t}\n\t\t} else {\n\t\t\tfor (let earlier = 0; earlier < index; earlier++) {\n\t\t\t\tif (conflicts(entries[earlier], current)) blockers.push(earlier);\n\t\t\t}\n\t\t\tpriorWriters.push(index);\n\t\t}\n\t\tdependencies.push(blockers);\n\t}\n\treturn dependencies;\n}\n"]}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Source-directed dependency scan with the read/read no-conflict invariant.
|
|
3
|
-
* The existing conflict predicate stays authoritative for paths, aliases,
|
|
4
|
-
* exclusive barriers, and custom resource kinds.
|
|
5
|
-
*
|
|
6
|
-
* O(n + n*w + E) time, O(n + E) memory, where w is the number of entries
|
|
7
|
-
* not proved read-only and E is the output edge count. Worst case stays O(n²).
|
|
8
|
-
* `isReadOnly` must mean that any two entries it accepts cannot conflict.
|
|
9
|
-
*/
|
|
10
|
-
export function buildReadAwareDependencies(entries, isReadOnly, conflicts) {
|
|
11
|
-
const priorWriters = [];
|
|
12
|
-
const dependencies = [];
|
|
13
|
-
for (let index = 0; index < entries.length; index++) {
|
|
14
|
-
const current = entries[index];
|
|
15
|
-
const readOnly = isReadOnly(current);
|
|
16
|
-
const blockers = [];
|
|
17
|
-
if (readOnly) {
|
|
18
|
-
for (const earlier of priorWriters) {
|
|
19
|
-
if (conflicts(entries[earlier], current))
|
|
20
|
-
blockers.push(earlier);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
for (let earlier = 0; earlier < index; earlier++) {
|
|
25
|
-
if (conflicts(entries[earlier], current))
|
|
26
|
-
blockers.push(earlier);
|
|
27
|
-
}
|
|
28
|
-
priorWriters.push(index);
|
|
29
|
-
}
|
|
30
|
-
dependencies.push(blockers);
|
|
31
|
-
}
|
|
32
|
-
return dependencies;
|
|
33
|
-
}
|
|
34
|
-
//# sourceMappingURL=tool-dag-read-aware.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tool-dag-read-aware.js","sourceRoot":"","sources":["../src/tool-dag-read-aware.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CACzC,OAAqB,EACrB,UAAiC,EACjC,SAA8C,EACjC;IACb,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,YAAY,GAAe,EAAE,CAAC;IACpC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,QAAQ,EAAE,CAAC;YACd,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;gBACpC,IAAI,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;oBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAClE,CAAC;QACF,CAAC;aAAM,CAAC;YACP,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;gBAClD,IAAI,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;oBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAClE,CAAC;YACD,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QACD,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,YAAY,CAAC;AAAA,CACpB","sourcesContent":["/**\n * Source-directed dependency scan with the read/read no-conflict invariant.\n * The existing conflict predicate stays authoritative for paths, aliases,\n * exclusive barriers, and custom resource kinds.\n *\n * O(n + n*w + E) time, O(n + E) memory, where w is the number of entries\n * not proved read-only and E is the output edge count. Worst case stays O(n²).\n * `isReadOnly` must mean that any two entries it accepts cannot conflict.\n */\nexport function buildReadAwareDependencies<T>(\n\tentries: readonly T[],\n\tisReadOnly: (entry: T) => boolean,\n\tconflicts: (earlier: T, current: T) => boolean,\n): number[][] {\n\tconst priorWriters: number[] = [];\n\tconst dependencies: number[][] = [];\n\tfor (let index = 0; index < entries.length; index++) {\n\t\tconst current = entries[index];\n\t\tconst readOnly = isReadOnly(current);\n\t\tconst blockers: number[] = [];\n\t\tif (readOnly) {\n\t\t\tfor (const earlier of priorWriters) {\n\t\t\t\tif (conflicts(entries[earlier], current)) blockers.push(earlier);\n\t\t\t}\n\t\t} else {\n\t\t\tfor (let earlier = 0; earlier < index; earlier++) {\n\t\t\t\tif (conflicts(entries[earlier], current)) blockers.push(earlier);\n\t\t\t}\n\t\t\tpriorWriters.push(index);\n\t\t}\n\t\tdependencies.push(blockers);\n\t}\n\treturn dependencies;\n}\n"]}
|