@openrouter/agent 0.8.0 → 0.9.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/README.md +483 -5
- package/esm/index.d.ts +19 -4
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +9 -2
- package/esm/index.js.map +1 -1
- package/esm/inner-loop/call-model.d.ts.map +1 -1
- package/esm/inner-loop/call-model.js +40 -41
- package/esm/inner-loop/call-model.js.map +1 -1
- package/esm/inner-loop/resume-tool-results.d.ts +128 -0
- package/esm/inner-loop/resume-tool-results.d.ts.map +1 -0
- package/esm/inner-loop/resume-tool-results.js +244 -0
- package/esm/inner-loop/resume-tool-results.js.map +1 -0
- package/esm/lib/agent-tool.d.ts +92 -0
- package/esm/lib/agent-tool.d.ts.map +1 -0
- package/esm/lib/agent-tool.js +211 -0
- package/esm/lib/agent-tool.js.map +1 -0
- package/esm/lib/async-params.d.ts +82 -0
- package/esm/lib/async-params.d.ts.map +1 -1
- package/esm/lib/async-params.js +5 -0
- package/esm/lib/async-params.js.map +1 -1
- package/esm/lib/async-tool-registry.d.ts +134 -0
- package/esm/lib/async-tool-registry.d.ts.map +1 -0
- package/esm/lib/async-tool-registry.js +352 -0
- package/esm/lib/async-tool-registry.js.map +1 -0
- package/esm/lib/async-tools.d.ts +23 -0
- package/esm/lib/async-tools.d.ts.map +1 -0
- package/esm/lib/async-tools.js +19 -0
- package/esm/lib/async-tools.js.map +1 -0
- package/esm/lib/conversation-state.d.ts +1 -0
- package/esm/lib/conversation-state.d.ts.map +1 -1
- package/esm/lib/conversation-state.js +8 -0
- package/esm/lib/conversation-state.js.map +1 -1
- package/esm/lib/doom-loop.d.ts +533 -0
- package/esm/lib/doom-loop.d.ts.map +1 -0
- package/esm/lib/doom-loop.js +1221 -0
- package/esm/lib/doom-loop.js.map +1 -0
- package/esm/lib/hooks-schemas.d.ts +33 -0
- package/esm/lib/hooks-schemas.d.ts.map +1 -1
- package/esm/lib/hooks-schemas.js +54 -0
- package/esm/lib/hooks-schemas.js.map +1 -1
- package/esm/lib/hooks-types.d.ts +6 -2
- package/esm/lib/hooks-types.d.ts.map +1 -1
- package/esm/lib/hooks-types.js.map +1 -1
- package/esm/lib/model-result.d.ts +646 -10
- package/esm/lib/model-result.d.ts.map +1 -1
- package/esm/lib/model-result.js +2749 -153
- package/esm/lib/model-result.js.map +1 -1
- package/esm/lib/reusable-stream.d.ts +9 -0
- package/esm/lib/reusable-stream.d.ts.map +1 -1
- package/esm/lib/reusable-stream.js +17 -0
- package/esm/lib/reusable-stream.js.map +1 -1
- package/esm/lib/stream-transformers.d.ts +20 -0
- package/esm/lib/stream-transformers.d.ts.map +1 -1
- package/esm/lib/stream-transformers.js +45 -0
- package/esm/lib/stream-transformers.js.map +1 -1
- package/esm/lib/tool-check.d.ts +76 -0
- package/esm/lib/tool-check.d.ts.map +1 -0
- package/esm/lib/tool-check.js +241 -0
- package/esm/lib/tool-check.js.map +1 -0
- package/esm/lib/tool-concurrency.d.ts +42 -0
- package/esm/lib/tool-concurrency.d.ts.map +1 -0
- package/esm/lib/tool-concurrency.js +80 -0
- package/esm/lib/tool-concurrency.js.map +1 -0
- package/esm/lib/tool-context.d.ts +45 -1
- package/esm/lib/tool-context.d.ts.map +1 -1
- package/esm/lib/tool-context.js +60 -1
- package/esm/lib/tool-context.js.map +1 -1
- package/esm/lib/tool-executor.d.ts +52 -9
- package/esm/lib/tool-executor.d.ts.map +1 -1
- package/esm/lib/tool-executor.js +253 -21
- package/esm/lib/tool-executor.js.map +1 -1
- package/esm/lib/tool-orchestrator.d.ts.map +1 -1
- package/esm/lib/tool-orchestrator.js +87 -50
- package/esm/lib/tool-orchestrator.js.map +1 -1
- package/esm/lib/tool-task.d.ts +157 -0
- package/esm/lib/tool-task.d.ts.map +1 -0
- package/esm/lib/tool-task.js +206 -0
- package/esm/lib/tool-task.js.map +1 -0
- package/esm/lib/tool-types.d.ts +473 -14
- package/esm/lib/tool-types.d.ts.map +1 -1
- package/esm/lib/tool-types.js +59 -10
- package/esm/lib/tool-types.js.map +1 -1
- package/esm/lib/tool.d.ts +184 -2
- package/esm/lib/tool.d.ts.map +1 -1
- package/esm/lib/tool.js +193 -3
- package/esm/lib/tool.js.map +1 -1
- package/package.json +30 -2
package/esm/index.js
CHANGED
|
@@ -5,14 +5,18 @@ export { SDKHooks } from '@openrouter/sdk/hooks/hooks';
|
|
|
5
5
|
// Message format compatibility helpers
|
|
6
6
|
// High-level model calling
|
|
7
7
|
export { callModel } from './inner-loop/call-model.js';
|
|
8
|
+
export { resumeToolResults, ToolTaskAlreadySettledError, } from './inner-loop/resume-tool-results.js';
|
|
9
|
+
export { AgentTranscriptSource } from './lib/agent-tool.js';
|
|
8
10
|
export { fromClaudeMessages, toClaudeMessage } from './lib/anthropic-compat.js';
|
|
9
11
|
export { hasAsyncFunctions, resolveAsyncFunctions } from './lib/async-params.js';
|
|
12
|
+
export { AsyncToolRegistry } from './lib/async-tool-registry.js';
|
|
10
13
|
export { fromChatMessages, toChatMessage } from './lib/chat-compat.js';
|
|
11
14
|
// Claude constants and type guards
|
|
12
15
|
export { ClaudeContentBlockType, NonClaudeMessageRole } from './lib/claude-constants.js';
|
|
13
16
|
export { isClaudeStyleMessages } from './lib/claude-type-guards.js';
|
|
14
17
|
// Conversation state helpers
|
|
15
18
|
export { appendToMessages, CONVERSATION_STATE_VERSION, createInitialState, createRejectedResult, createUnsentResult, deserializeConversationState, generateConversationId, InvalidStateError, partitionToolCalls, serializeConversationState, toolRequiresApproval, UnsupportedStateVersionError, updateState, } from './lib/conversation-state.js';
|
|
19
|
+
export { canonicalizeKeyMaterial, DEFAULT_DOOM_LOOP_LADDER, DEFAULT_MAX_ESCALATIONS, DoomLoopMonitor, detectTextRepetition, fingerprintKeyMaterial, fingerprintToolCall, MAX_CANONICALIZE_DEPTH, resolveDoomLoopOption, resolveLoopKeyMaterial, } from './lib/doom-loop.js';
|
|
16
20
|
// Lifecycle hooks system (PreToolUse, PostToolUse, Stop, SessionStart, ...).
|
|
17
21
|
// Distinct from the SDK transport hooks re-exported above (SDKHooks,
|
|
18
22
|
// BeforeRequestHook, HookContext, ...), which intercept HTTP requests.
|
|
@@ -27,13 +31,16 @@ export { applyNextTurnParamsToRequest, buildNextTurnParamsContext, executeNextTu
|
|
|
27
31
|
// Stop condition helpers
|
|
28
32
|
export { finishReasonIs, hasToolCall, isStopConditionMet, maxCost, maxTokensUsed, stepCountIs, } from './lib/stop-conditions.js';
|
|
29
33
|
export { extractUnsupportedContent, getUnsupportedContentSummary, hasUnsupportedContent, } from './lib/stream-transformers.js';
|
|
30
|
-
// Tool creation helpers
|
|
34
|
+
// Tool creation helpers (tool also carries tool.background / tool.deferred)
|
|
31
35
|
export { markMcp, serverTool, tool } from './lib/tool.js';
|
|
36
|
+
export { TASK_TOOL_NAME, TaskToolInputSchema } from './lib/tool-check.js';
|
|
37
|
+
export { acquireAll, Semaphore } from './lib/tool-concurrency.js';
|
|
32
38
|
// Tool context helpers
|
|
33
39
|
export { buildToolExecuteContext, ToolContextStore } from './lib/tool-context.js';
|
|
34
40
|
// Real-time tool event broadcasting
|
|
35
41
|
export { ToolEventBroadcaster } from './lib/tool-event-broadcaster.js';
|
|
36
|
-
export {
|
|
42
|
+
export { DEFAULT_TASK_LOG_LIMITS, ToolTask } from './lib/tool-task.js';
|
|
43
|
+
export { hasApprovalRequiredTools, hasExecuteFunction, isAgentTool, isAutoResolvableTool, isClientTool, isDeferredHandle, isGeneratorTool, isHITLTool, isLongRunningTool, isManualTool, isMcpTool, isRegularExecuteTool, isServerTool, isToolAsyncSettledEvent, isToolAsyncStartedEvent, isToolCallOutputEvent, isToolPreliminaryResultEvent, isToolResultEvent, isTurnEndEvent, isTurnStartEvent, isUnifiedTool, ToolType, toolHasApprovalConfigured, } from './lib/tool-types.js';
|
|
37
44
|
// Turn context helpers
|
|
38
45
|
export { buildTurnContext, normalizeInputToArray } from './lib/turn-context.js';
|
|
39
46
|
export { OpenRouter } from './openrouter.js';
|
package/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,iEAAiE;AAEjE,yDAAyD;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AA2EvD,uCAAuC;AAEvC,2BAA2B;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-model.d.ts","sourceRoot":"","sources":["../../src/inner-loop/call-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,IAAI,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAG7D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"call-model.d.ts","sourceRoot":"","sources":["../../src/inner-loop/call-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,IAAI,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAG7D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGrD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAGjD,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AACH,wBAAgB,SAAS,CACvB,MAAM,SAAS,SAAS,IAAI,EAAE,EAC9B,aAAa,SAAS,UAAU,CAAC,SAAS,CAAC,GAAG,SAAS,GAAG,SAAS,EACnE,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,SAAS,UAAU,CAAC,SAAS,CAAC,GACjF,QAAQ,CAAC,aAAa,CAAC,GACvB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAEzB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACzC,mBAAmB,CAAC,EAAE,aAAa,CAAC;CACrC,EACD,OAAO,CAAC,EAAE,cAAc,GACvB,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CA8F9B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveHooks } from '../lib/hooks-resolve.js';
|
|
2
2
|
import { ModelResult } from '../lib/model-result.js';
|
|
3
|
-
import {
|
|
3
|
+
import { buildTaskToolApiDefinition, needsTaskTool } from '../lib/tool-check.js';
|
|
4
|
+
import { convertToolsToAPIFormat, convertZodToJsonSchema } from '../lib/tool-executor.js';
|
|
4
5
|
/**
|
|
5
6
|
* Get a response with multiple consumption patterns
|
|
6
7
|
*
|
|
@@ -71,9 +72,18 @@ import { convertToolsToAPIFormat } from '../lib/tool-executor.js';
|
|
|
71
72
|
*/
|
|
72
73
|
export function callModel(client, request, options) {
|
|
73
74
|
// Destructure state management options along with tools and stopWhen
|
|
74
|
-
const { tools, stopWhen, state, requireApproval, approveToolCalls, rejectToolCalls, context, sharedContextSchema, onTurnStart, onTurnEnd, allowFinalResponse, strictFinalResponse, hooks, ...apiRequest } = request;
|
|
75
|
+
const { tools, stopWhen, state, requireApproval, approveToolCalls, rejectToolCalls, context, sharedContextSchema, onTurnStart, onTurnEnd, allowFinalResponse, strictFinalResponse, hooks, doomLoop, signal, toolTimeoutMs, toolConcurrency, asyncTools, ...apiRequest } = request;
|
|
75
76
|
// Convert tools to API format - no cast needed now that convertToolsToAPIFormat accepts readonly
|
|
76
77
|
const apiTools = tools ? convertToolsToAPIFormat(tools) : undefined;
|
|
78
|
+
// Append the single universal `task` tool when any long-running tool is
|
|
79
|
+
// registered (and check-ins aren't disabled): ONE static wire definition
|
|
80
|
+
// for check/steer/result/cancel across every async tool — per-tool
|
|
81
|
+
// context cost stays constant regardless of the tool count. Appended
|
|
82
|
+
// here (not per-request in ModelResult) so `resolvedRequest.tools` stays
|
|
83
|
+
// stable across turns. Calls to it are engine-intercepted.
|
|
84
|
+
if (apiTools && tools && asyncTools?.checkins !== false && needsTaskTool(tools)) {
|
|
85
|
+
apiTools.push(buildTaskToolApiDefinition(convertZodToJsonSchema));
|
|
86
|
+
}
|
|
77
87
|
// Build the request with converted tools
|
|
78
88
|
// Note: async functions are resolved later in ModelResult.executeToolsIfNeeded()
|
|
79
89
|
// The request can have async fields (functions) or sync fields, and the tools are converted to API format
|
|
@@ -91,48 +101,37 @@ export function callModel(client, request, options) {
|
|
|
91
101
|
'x-openrouter-callmodel': 'true',
|
|
92
102
|
},
|
|
93
103
|
};
|
|
94
|
-
|
|
104
|
+
// Assemble engine options, then drop undefined entries so optional keys
|
|
105
|
+
// are ABSENT (not present-as-undefined) — same semantics as the previous
|
|
106
|
+
// per-key conditional spreads without a branch per option.
|
|
107
|
+
const engineOptions = {
|
|
95
108
|
client,
|
|
96
109
|
request: finalRequest,
|
|
97
110
|
options: callModelOptions,
|
|
98
111
|
tools,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}),
|
|
124
|
-
...(onTurnEnd !== undefined && {
|
|
125
|
-
onTurnEnd,
|
|
126
|
-
}),
|
|
127
|
-
...(allowFinalResponse !== undefined && {
|
|
128
|
-
allowFinalResponse,
|
|
129
|
-
}),
|
|
130
|
-
...(strictFinalResponse !== undefined && {
|
|
131
|
-
strictFinalResponse,
|
|
132
|
-
}),
|
|
133
|
-
...(hooks !== undefined && {
|
|
134
|
-
hooks: resolveHooks(hooks),
|
|
135
|
-
}),
|
|
136
|
-
});
|
|
112
|
+
stopWhen,
|
|
113
|
+
state,
|
|
114
|
+
requireApproval,
|
|
115
|
+
approveToolCalls,
|
|
116
|
+
rejectToolCalls,
|
|
117
|
+
context,
|
|
118
|
+
sharedContextSchema,
|
|
119
|
+
onTurnStart,
|
|
120
|
+
onTurnEnd,
|
|
121
|
+
allowFinalResponse,
|
|
122
|
+
strictFinalResponse,
|
|
123
|
+
hooks: hooks !== undefined ? resolveHooks(hooks) : undefined,
|
|
124
|
+
doomLoop,
|
|
125
|
+
signal,
|
|
126
|
+
toolTimeoutMs,
|
|
127
|
+
toolConcurrency,
|
|
128
|
+
asyncTools,
|
|
129
|
+
};
|
|
130
|
+
for (const key of Object.keys(engineOptions)) {
|
|
131
|
+
if (engineOptions[key] === undefined) {
|
|
132
|
+
delete engineOptions[key];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return new ModelResult(engineOptions);
|
|
137
136
|
}
|
|
138
137
|
//# sourceMappingURL=call-model.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-model.js","sourceRoot":"","sources":["../../src/inner-loop/call-model.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"call-model.js","sourceRoot":"","sources":["../../src/inner-loop/call-model.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAM1F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AACH,MAAM,UAAU,SAAS,CAOvB,MAAsB,EACtB,OAEC,EACD,OAAwB;IAExB,qEAAqE;IACrE,MAAM,EACJ,KAAK,EACL,QAAQ,EACR,KAAK,EACL,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,OAAO,EACP,mBAAmB,EACnB,WAAW,EACX,SAAS,EACT,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,EACL,QAAQ,EACR,MAAM,EACN,aAAa,EACb,eAAe,EACf,UAAU,EACV,GAAG,UAAU,EACd,GAAG,OAAO,CAAC;IAEZ,iGAAiG;IACjG,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEpE,wEAAwE;IACxE,yEAAyE;IACzE,mEAAmE;IACnE,qEAAqE;IACrE,yEAAyE;IACzE,2DAA2D;IAC3D,IAAI,QAAQ,IAAI,KAAK,IAAI,UAAU,EAAE,QAAQ,KAAK,KAAK,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QAChF,QAAQ,CAAC,IAAI,CAAC,0BAA0B,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,yCAAyC;IACzC,iFAAiF;IACjF,0GAA0G;IAC1G,MAAM,YAAY,GAA4B;QAC5C,GAAG,UAAU;KACd,CAAC;IAEF,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,YAAY,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC;IACnC,CAAC;IAED,oEAAoE;IACpE,MAAM,gBAAgB,GAAmB;QACvC,GAAG,OAAO;QACV,OAAO,EAAE;YACP,GAAG,MAAM,CAAC,WAAW,CACnB,IAAI,OAAO,CAAC,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,YAAY,EAAE,OAAO,IAAI,SAAS,CAAC,CAC7E;YACD,wBAAwB,EAAE,MAAM;SACjC;KACF,CAAC;IAEF,wEAAwE;IACxE,yEAAyE;IACzE,2DAA2D;IAC3D,MAAM,aAAa,GAA4B;QAC7C,MAAM;QACN,OAAO,EAAE,YAAY;QACrB,OAAO,EAAE,gBAAgB;QACzB,KAAK;QACL,QAAQ;QACR,KAAK;QACL,eAAe;QACf,gBAAgB;QAChB,eAAe;QACf,OAAO;QACP,mBAAmB;QACnB,WAAW;QACX,SAAS;QACT,kBAAkB;QAClB,mBAAmB;QACnB,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;QAC5D,QAAQ;QACR,MAAM;QACN,aAAa;QACb,eAAe;QACf,UAAU;KACX,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QAC7C,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;YACrC,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,IAAI,WAAW,CACpB,aAA+D,CAChE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { OpenRouterCore } from '@openrouter/sdk/core';
|
|
2
|
+
import type { RequestOptions } from '@openrouter/sdk/lib/sdks';
|
|
3
|
+
import type * as models from '@openrouter/sdk/models';
|
|
4
|
+
import type { CallModelInput } from '../lib/async-params.js';
|
|
5
|
+
import type { ModelResult } from '../lib/model-result.js';
|
|
6
|
+
import type { StateAccessor, Tool } from '../lib/tool-types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Thrown by {@link resumeToolResults} (and the deferred tool `.resolve()` /
|
|
9
|
+
* `.fail()` / `.cancel()` methods) when the target task was already settled —
|
|
10
|
+
* the at-most-once guard against double resolution and replayed webhooks.
|
|
11
|
+
* Opt out per call with `ifSettled: 'ignore'`.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ToolTaskAlreadySettledError extends Error {
|
|
14
|
+
readonly taskId: string;
|
|
15
|
+
readonly callId: string;
|
|
16
|
+
constructor(taskId: string, callId: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* One task resolution: identified by `taskId` (typical — it's what the
|
|
20
|
+
* external system knows) or `callId`, carrying either a successful `output`
|
|
21
|
+
* or an `error`. `status` refines error deliveries (default `'failed'`).
|
|
22
|
+
*/
|
|
23
|
+
export type ResumeToolResultEntry = ({
|
|
24
|
+
taskId: string;
|
|
25
|
+
callId?: never;
|
|
26
|
+
} | {
|
|
27
|
+
callId: string;
|
|
28
|
+
taskId?: never;
|
|
29
|
+
}) & ({
|
|
30
|
+
output: unknown;
|
|
31
|
+
error?: never;
|
|
32
|
+
} | {
|
|
33
|
+
error: string;
|
|
34
|
+
output?: never;
|
|
35
|
+
status?: 'failed' | 'cancelled' | 'expired';
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Run configuration for continuing the conversation immediately after
|
|
39
|
+
* recording the results. Any `CallModelInput` field except `state` / `input`
|
|
40
|
+
* (the SDK supplies both — the resumed history already carries everything).
|
|
41
|
+
*/
|
|
42
|
+
export type ResumeRunConfig<TTools extends readonly Tool[]> = Omit<CallModelInput<TTools>, 'state' | 'input' | 'approveToolCalls' | 'rejectToolCalls'>;
|
|
43
|
+
/**
|
|
44
|
+
* The `tool_task_result` envelope injected as a user-role message when an
|
|
45
|
+
* async tool's result arrives. Never a second `function_call_output` — the
|
|
46
|
+
* pending placeholder already paired the original `function_call` in
|
|
47
|
+
* history, and providers reject duplicate outputs for one callId.
|
|
48
|
+
*/
|
|
49
|
+
export interface ToolTaskResultEnvelope {
|
|
50
|
+
type: 'tool_task_result';
|
|
51
|
+
tool: string;
|
|
52
|
+
taskId: string;
|
|
53
|
+
callId: string;
|
|
54
|
+
status: 'completed' | 'failed' | 'cancelled' | 'timed_out' | 'expired';
|
|
55
|
+
result?: unknown;
|
|
56
|
+
error?: string;
|
|
57
|
+
}
|
|
58
|
+
/** Build the user-role message carrying a task-result envelope. */
|
|
59
|
+
export declare function buildTaskResultMessage(envelope: ToolTaskResultEnvelope): models.BaseInputsUnion;
|
|
60
|
+
/**
|
|
61
|
+
* Deliver results for pending async tool tasks (started by `tool.deferred`,
|
|
62
|
+
* or background tasks orphaned across a run boundary) into a persisted
|
|
63
|
+
* conversation — typically from a different process than the one that
|
|
64
|
+
* started them (a webhook handler, a queue worker).
|
|
65
|
+
*
|
|
66
|
+
* This is the low-level, untyped entry point; prefer the typed `.resolve()`
|
|
67
|
+
* / `.fail()` / `.cancel()` methods on the deferred tool itself, which
|
|
68
|
+
* check `output` against the tool's `outputSchema` at compile time.
|
|
69
|
+
*
|
|
70
|
+
* Behavior:
|
|
71
|
+
* 1. Loads the conversation via `state`, resolves each entry to a pending
|
|
72
|
+
* task (by `taskId` or `callId`).
|
|
73
|
+
* 2. Enforces at-most-once settlement: an already-settled task throws
|
|
74
|
+
* {@link ToolTaskAlreadySettledError} (or is skipped with
|
|
75
|
+
* `ifSettled: 'ignore'`).
|
|
76
|
+
* 3. Validates each `output` against the named tool's `outputSchema` when
|
|
77
|
+
* the tool is present in `tools` — an invalid payload throws before
|
|
78
|
+
* anything is persisted or dispatched.
|
|
79
|
+
* 4. Appends a `tool_task_result` envelope (user-role message) per entry,
|
|
80
|
+
* marks the tasks settled, and persists the state. The conversation
|
|
81
|
+
* status becomes `'in_progress'` (or stays `'awaiting_async_tools'` when
|
|
82
|
+
* other tasks are still pending).
|
|
83
|
+
* 5. With `run` config: continues the conversation immediately and returns
|
|
84
|
+
* the `ModelResult`. Without: returns `null` — the recorded results ride
|
|
85
|
+
* along on the next `callModel({ state })`. When every entry was skipped
|
|
86
|
+
* under `ifSettled: 'ignore'` there is nothing new to deliver, so the
|
|
87
|
+
* function returns `null` WITHOUT running — a replayed webhook never
|
|
88
|
+
* triggers a duplicate continuation.
|
|
89
|
+
*
|
|
90
|
+
* SECURITY: this call injects a value the model will treat as a tool
|
|
91
|
+
* result. Authenticate the webhook/caller BEFORE invoking it — the SDK
|
|
92
|
+
* cannot do that for you. And PASS `tools`: without it the output is
|
|
93
|
+
* appended UNVALIDATED (no outputSchema to check against) — prefer the
|
|
94
|
+
* typed `.resolve()` methods, which always carry the tool's schema.
|
|
95
|
+
*
|
|
96
|
+
* CONCURRENCY: this is a read-modify-write over the StateAccessor. Two
|
|
97
|
+
* concurrent calls for the SAME conversation (e.g. two webhooks resolving
|
|
98
|
+
* two different tasks) can interleave load/save and the later save wins,
|
|
99
|
+
* silently dropping the earlier envelope. Serialize calls per conversation
|
|
100
|
+
* — a per-conversation lock or queue in your StateAccessor, or batch
|
|
101
|
+
* concurrent completions into one call via `results: [...]` (which settles
|
|
102
|
+
* any number of tasks atomically).
|
|
103
|
+
*
|
|
104
|
+
* HOOKS: PostToolUse / PostToolUseFailure do NOT fire here — hooks are
|
|
105
|
+
* run-scoped (a HooksManager passed to `callModel`), and this entry point
|
|
106
|
+
* runs outside any run, typically in another process. Deployments that
|
|
107
|
+
* audit tool outputs via hooks should scan webhook payloads in the webhook
|
|
108
|
+
* handler itself. In-process settlements (background tasks) DO fire these
|
|
109
|
+
* hooks at delivery.
|
|
110
|
+
*/
|
|
111
|
+
export declare function resumeToolResults<TTools extends readonly Tool[]>(client: OpenRouterCore, request: {
|
|
112
|
+
state: StateAccessor<TTools>;
|
|
113
|
+
/** Tools list — used to validate outputs against each tool's outputSchema. */
|
|
114
|
+
tools?: TTools;
|
|
115
|
+
results: ReadonlyArray<ResumeToolResultEntry>;
|
|
116
|
+
/** Behavior when a task is already settled. Default 'throw'. */
|
|
117
|
+
ifSettled?: 'throw' | 'ignore';
|
|
118
|
+
/** Continue the conversation immediately with this run configuration. */
|
|
119
|
+
run?: ResumeRunConfig<TTools>;
|
|
120
|
+
/**
|
|
121
|
+
* Ownership guard: every resolved task must belong to this tool (by
|
|
122
|
+
* name) or the call throws BEFORE anything is persisted. Set by the
|
|
123
|
+
* typed `.resolve()`/`.fail()`/`.cancel()` methods — a taskId from a
|
|
124
|
+
* webhook cannot settle a different tool's task through them.
|
|
125
|
+
*/
|
|
126
|
+
expectToolName?: string;
|
|
127
|
+
}, options?: RequestOptions): Promise<ModelResult<TTools> | null>;
|
|
128
|
+
//# sourceMappingURL=resume-tool-results.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume-tool-results.d.ts","sourceRoot":"","sources":["../../src/inner-loop/resume-tool-results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,KAAK,MAAM,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG1D,OAAO,KAAK,EAAoB,aAAa,EAAE,IAAI,EAAkB,MAAM,sBAAsB,CAAC;AAIlG;;;;;GAKG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAM3C;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAChC;IACE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB,GACD;IACE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB,CACJ,GACC,CACI;IACE,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,GACD;IACE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,MAAM,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;CAC7C,CACJ,CAAC;AAEJ;;;;GAIG;AACH,MAAM,MAAM,eAAe,CAAC,MAAM,SAAS,SAAS,IAAI,EAAE,IAAI,IAAI,CAChE,cAAc,CAAC,MAAM,CAAC,EACtB,OAAO,GAAG,OAAO,GAAG,kBAAkB,GAAG,iBAAiB,CAC3D,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,kBAAkB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAC;IACvE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,mEAAmE;AACnE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,CAAC,eAAe,CAK/F;AAqDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,SAAS,SAAS,IAAI,EAAE,EACpE,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE;IACP,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAC9C,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC/B,yEAAyE;IACzE,GAAG,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAC9B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,EACD,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAkHrC"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { appendToMessages, updateState } from '../lib/conversation-state.js';
|
|
2
|
+
import { validateToolOutput } from '../lib/tool-executor.js';
|
|
3
|
+
import { TASK_RESULT_BOUNDARY } from '../lib/tool-task.js';
|
|
4
|
+
import { isClientTool, isUnifiedTool } from '../lib/tool-types.js';
|
|
5
|
+
import { callModel } from './call-model.js';
|
|
6
|
+
/**
|
|
7
|
+
* Thrown by {@link resumeToolResults} (and the deferred tool `.resolve()` /
|
|
8
|
+
* `.fail()` / `.cancel()` methods) when the target task was already settled —
|
|
9
|
+
* the at-most-once guard against double resolution and replayed webhooks.
|
|
10
|
+
* Opt out per call with `ifSettled: 'ignore'`.
|
|
11
|
+
*/
|
|
12
|
+
export class ToolTaskAlreadySettledError extends Error {
|
|
13
|
+
constructor(taskId, callId) {
|
|
14
|
+
super(`Tool task "${taskId}" (call ${callId}) has already been settled`);
|
|
15
|
+
this.name = 'ToolTaskAlreadySettledError';
|
|
16
|
+
this.taskId = taskId;
|
|
17
|
+
this.callId = callId;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Build the user-role message carrying a task-result envelope. */
|
|
21
|
+
export function buildTaskResultMessage(envelope) {
|
|
22
|
+
return {
|
|
23
|
+
role: 'user',
|
|
24
|
+
content: `${TASK_RESULT_BOUNDARY}\n${JSON.stringify(envelope)}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Find the pending task an incoming result belongs to, or `null` when the entry
|
|
29
|
+
* is already settled and the caller asked to ignore that.
|
|
30
|
+
*
|
|
31
|
+
* Settled tasks deliberately STAY in `pendingAsyncTools` with a terminal
|
|
32
|
+
* status, so a replayed webhook resolves to `ToolTaskAlreadySettledError`
|
|
33
|
+
* rather than "not found".
|
|
34
|
+
*
|
|
35
|
+
* @throws ToolTaskAlreadySettledError when the task already settled and
|
|
36
|
+
* `ifSettled` is `'throw'` (the default).
|
|
37
|
+
* @throws Error when no pending task matches the entry at all.
|
|
38
|
+
*/
|
|
39
|
+
function resolvePendingTask({ entry, pending, settledIds, ifSettled, }) {
|
|
40
|
+
const task = pending.find((t) => 'taskId' in entry && entry.taskId !== undefined
|
|
41
|
+
? t.taskId === entry.taskId
|
|
42
|
+
: t.callId === entry.callId);
|
|
43
|
+
if (!task) {
|
|
44
|
+
// The callId fallback covers states persisted by older SDK versions whose
|
|
45
|
+
// in-process delivery REMOVED the entry, leaving only the callId on
|
|
46
|
+
// settledAsyncCallIds.
|
|
47
|
+
if (entry.callId !== undefined && settledIds.has(entry.callId)) {
|
|
48
|
+
if (ifSettled === 'ignore') {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
throw new ToolTaskAlreadySettledError(entry.taskId ?? entry.callId, entry.callId);
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`resumeToolResults: no pending async tool task found for "${entry.taskId ?? entry.callId}"`);
|
|
54
|
+
}
|
|
55
|
+
if (settledIds.has(task.callId) || task.status !== 'working') {
|
|
56
|
+
if (ifSettled === 'ignore') {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
throw new ToolTaskAlreadySettledError(task.taskId, task.callId);
|
|
60
|
+
}
|
|
61
|
+
return task;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Deliver results for pending async tool tasks (started by `tool.deferred`,
|
|
65
|
+
* or background tasks orphaned across a run boundary) into a persisted
|
|
66
|
+
* conversation — typically from a different process than the one that
|
|
67
|
+
* started them (a webhook handler, a queue worker).
|
|
68
|
+
*
|
|
69
|
+
* This is the low-level, untyped entry point; prefer the typed `.resolve()`
|
|
70
|
+
* / `.fail()` / `.cancel()` methods on the deferred tool itself, which
|
|
71
|
+
* check `output` against the tool's `outputSchema` at compile time.
|
|
72
|
+
*
|
|
73
|
+
* Behavior:
|
|
74
|
+
* 1. Loads the conversation via `state`, resolves each entry to a pending
|
|
75
|
+
* task (by `taskId` or `callId`).
|
|
76
|
+
* 2. Enforces at-most-once settlement: an already-settled task throws
|
|
77
|
+
* {@link ToolTaskAlreadySettledError} (or is skipped with
|
|
78
|
+
* `ifSettled: 'ignore'`).
|
|
79
|
+
* 3. Validates each `output` against the named tool's `outputSchema` when
|
|
80
|
+
* the tool is present in `tools` — an invalid payload throws before
|
|
81
|
+
* anything is persisted or dispatched.
|
|
82
|
+
* 4. Appends a `tool_task_result` envelope (user-role message) per entry,
|
|
83
|
+
* marks the tasks settled, and persists the state. The conversation
|
|
84
|
+
* status becomes `'in_progress'` (or stays `'awaiting_async_tools'` when
|
|
85
|
+
* other tasks are still pending).
|
|
86
|
+
* 5. With `run` config: continues the conversation immediately and returns
|
|
87
|
+
* the `ModelResult`. Without: returns `null` — the recorded results ride
|
|
88
|
+
* along on the next `callModel({ state })`. When every entry was skipped
|
|
89
|
+
* under `ifSettled: 'ignore'` there is nothing new to deliver, so the
|
|
90
|
+
* function returns `null` WITHOUT running — a replayed webhook never
|
|
91
|
+
* triggers a duplicate continuation.
|
|
92
|
+
*
|
|
93
|
+
* SECURITY: this call injects a value the model will treat as a tool
|
|
94
|
+
* result. Authenticate the webhook/caller BEFORE invoking it — the SDK
|
|
95
|
+
* cannot do that for you. And PASS `tools`: without it the output is
|
|
96
|
+
* appended UNVALIDATED (no outputSchema to check against) — prefer the
|
|
97
|
+
* typed `.resolve()` methods, which always carry the tool's schema.
|
|
98
|
+
*
|
|
99
|
+
* CONCURRENCY: this is a read-modify-write over the StateAccessor. Two
|
|
100
|
+
* concurrent calls for the SAME conversation (e.g. two webhooks resolving
|
|
101
|
+
* two different tasks) can interleave load/save and the later save wins,
|
|
102
|
+
* silently dropping the earlier envelope. Serialize calls per conversation
|
|
103
|
+
* — a per-conversation lock or queue in your StateAccessor, or batch
|
|
104
|
+
* concurrent completions into one call via `results: [...]` (which settles
|
|
105
|
+
* any number of tasks atomically).
|
|
106
|
+
*
|
|
107
|
+
* HOOKS: PostToolUse / PostToolUseFailure do NOT fire here — hooks are
|
|
108
|
+
* run-scoped (a HooksManager passed to `callModel`), and this entry point
|
|
109
|
+
* runs outside any run, typically in another process. Deployments that
|
|
110
|
+
* audit tool outputs via hooks should scan webhook payloads in the webhook
|
|
111
|
+
* handler itself. In-process settlements (background tasks) DO fire these
|
|
112
|
+
* hooks at delivery.
|
|
113
|
+
*/
|
|
114
|
+
export async function resumeToolResults(client, request, options) {
|
|
115
|
+
const state = await request.state.load();
|
|
116
|
+
if (!state) {
|
|
117
|
+
throw new Error('resumeToolResults: no conversation state found for the given StateAccessor');
|
|
118
|
+
}
|
|
119
|
+
const pending = state.pendingAsyncTools ?? [];
|
|
120
|
+
const settledIds = new Set(state.settledAsyncCallIds ?? []);
|
|
121
|
+
const envelopes = [];
|
|
122
|
+
/** callId → the terminal lifecycle status persisted for that entry. */
|
|
123
|
+
const settledNow = new Map();
|
|
124
|
+
for (const entry of request.results) {
|
|
125
|
+
const task = resolvePendingTask({
|
|
126
|
+
entry,
|
|
127
|
+
pending,
|
|
128
|
+
settledIds,
|
|
129
|
+
ifSettled: request.ifSettled,
|
|
130
|
+
});
|
|
131
|
+
if (!task) {
|
|
132
|
+
continue; // already settled and the caller asked to ignore it
|
|
133
|
+
}
|
|
134
|
+
// Ownership guard: a taskId is an external identifier handed to
|
|
135
|
+
// third-party systems — a lower-trust webhook must not be able to
|
|
136
|
+
// settle a DIFFERENT tool's task through a tool-bound completion
|
|
137
|
+
// method (confused deputy; it would also skip that tool's
|
|
138
|
+
// outputSchema validation).
|
|
139
|
+
if (request.expectToolName !== undefined && task.name !== request.expectToolName) {
|
|
140
|
+
throw new Error(`resumeToolResults: task "${task.taskId}" belongs to tool "${task.name}", not "${request.expectToolName}" — use the owning tool's completion methods (or the untyped resumeToolResults entry point).`);
|
|
141
|
+
}
|
|
142
|
+
const envelope = buildResumeEnvelope(entry, task, request.tools);
|
|
143
|
+
envelopes.push(buildTaskResultMessage(envelope));
|
|
144
|
+
// Persist the entry's real terminal status. 'expired' / 'timed_out'
|
|
145
|
+
// have no ToolTaskStatus member — they persist as 'failed'.
|
|
146
|
+
settledNow.set(task.callId, envelope.status === 'completed' || envelope.status === 'cancelled'
|
|
147
|
+
? envelope.status
|
|
148
|
+
: 'failed');
|
|
149
|
+
settledIds.add(task.callId);
|
|
150
|
+
}
|
|
151
|
+
if (envelopes.length === 0) {
|
|
152
|
+
// Every entry was already settled and ignored — nothing to record.
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
// Keep settled entries with a terminal status (rather than dropping them)
|
|
156
|
+
// so a replayed resolution surfaces as ToolTaskAlreadySettledError.
|
|
157
|
+
const nextPending = pending.map((t) => {
|
|
158
|
+
const terminalStatus = settledNow.get(t.callId);
|
|
159
|
+
return terminalStatus !== undefined
|
|
160
|
+
? {
|
|
161
|
+
...t,
|
|
162
|
+
status: terminalStatus,
|
|
163
|
+
}
|
|
164
|
+
: t;
|
|
165
|
+
});
|
|
166
|
+
// Status transition rules:
|
|
167
|
+
// - Orphaned (detached) tasks never settle — counting them as working
|
|
168
|
+
// would leave the conversation reporting 'awaiting_async_tools' forever.
|
|
169
|
+
// - Only advance a status this delivery OWNS. A conversation can hold a
|
|
170
|
+
// non-async pause (awaiting_approval / awaiting_hitl /
|
|
171
|
+
// awaiting_client_tools) while async tasks are outstanding; clobbering
|
|
172
|
+
// it to 'in_progress' would make the engine skip the decision-resume
|
|
173
|
+
// path and drop the caller's approvals.
|
|
174
|
+
// - A 'complete' conversation is only reopened when the caller asked to
|
|
175
|
+
// continue (`run` config). Record-only delivery on a finished
|
|
176
|
+
// conversation keeps it finished — the envelope rides along if the
|
|
177
|
+
// caller ever resumes it.
|
|
178
|
+
const stillWorking = nextPending.some((t) => t.status === 'working' && t.orphaned !== true);
|
|
179
|
+
const statusOwnedByAsyncDelivery = state.status === 'awaiting_async_tools' ||
|
|
180
|
+
state.status === 'in_progress' ||
|
|
181
|
+
(state.status === 'complete' && request.run !== undefined) ||
|
|
182
|
+
state.status === undefined;
|
|
183
|
+
const updated = updateState(state, {
|
|
184
|
+
messages: appendToMessages(state.messages, envelopes),
|
|
185
|
+
settledAsyncCallIds: [
|
|
186
|
+
...(state.settledAsyncCallIds ?? []),
|
|
187
|
+
...settledNow.keys(),
|
|
188
|
+
],
|
|
189
|
+
...(statusOwnedByAsyncDelivery && {
|
|
190
|
+
status: stillWorking ? 'awaiting_async_tools' : 'in_progress',
|
|
191
|
+
}),
|
|
192
|
+
pendingAsyncTools: nextPending,
|
|
193
|
+
});
|
|
194
|
+
await request.state.save(updated);
|
|
195
|
+
if (!request.run) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
// Continue the conversation: the envelopes are already in persisted
|
|
199
|
+
// history, so no fresh input is supplied.
|
|
200
|
+
return callModel(client, {
|
|
201
|
+
...request.run,
|
|
202
|
+
input: [],
|
|
203
|
+
tools: request.run.tools ?? request.tools,
|
|
204
|
+
state: request.state,
|
|
205
|
+
}, options);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Build the `tool_task_result` envelope for one resume entry. Successful
|
|
209
|
+
* outputs are validated against the owning tool's `outputSchema` when the
|
|
210
|
+
* tool is available; error entries carry the caller's refined status
|
|
211
|
+
* (default `'failed'`).
|
|
212
|
+
*/
|
|
213
|
+
function buildResumeEnvelope(entry, task, tools) {
|
|
214
|
+
if ('output' in entry && entry.error === undefined) {
|
|
215
|
+
const tool = tools?.find((t) => isClientTool(t) && t.function.name === task.name);
|
|
216
|
+
// Fail closed: when a tools list was supplied but the owning tool is
|
|
217
|
+
// not in it, the caller expected validation — appending the output
|
|
218
|
+
// unvalidated would silently void that expectation.
|
|
219
|
+
if (tools !== undefined && tool === undefined) {
|
|
220
|
+
throw new Error(`resumeToolResults: task "${task.taskId}" belongs to tool "${task.name}", which is not in the supplied tools list — its output cannot be validated. Include the tool, or omit \`tools\` to skip validation explicitly.`);
|
|
221
|
+
}
|
|
222
|
+
let output = entry.output;
|
|
223
|
+
if (tool && isUnifiedTool(tool) && tool.function.outputSchema !== undefined) {
|
|
224
|
+
output = validateToolOutput(tool.function.outputSchema, output);
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
type: 'tool_task_result',
|
|
228
|
+
tool: task.name,
|
|
229
|
+
taskId: task.taskId,
|
|
230
|
+
callId: task.callId,
|
|
231
|
+
status: 'completed',
|
|
232
|
+
result: output,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
type: 'tool_task_result',
|
|
237
|
+
tool: task.name,
|
|
238
|
+
taskId: task.taskId,
|
|
239
|
+
callId: task.callId,
|
|
240
|
+
status: entry.status ?? 'failed',
|
|
241
|
+
error: entry.error ?? 'Task failed',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=resume-tool-results.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume-tool-results.js","sourceRoot":"","sources":["../../src/inner-loop/resume-tool-results.ts"],"names":[],"mappings":"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"}
|