@openrouter/agent 0.8.0 → 0.10.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 +732 -7
- package/THIRD_PARTY_NOTICES.md +25 -0
- package/esm/index.d.ts +20 -5
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +10 -3
- 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 +63 -46
- 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 +96 -0
- package/esm/lib/async-params.d.ts.map +1 -1
- package/esm/lib/async-params.js +30 -1
- 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 +648 -12
- package/esm/lib/model-result.d.ts.map +1 -1
- package/esm/lib/model-result.js +2784 -164
- package/esm/lib/model-result.js.map +1 -1
- package/esm/lib/next-turn-params.d.ts.map +1 -1
- package/esm/lib/next-turn-params.js +3 -1
- package/esm/lib/next-turn-params.js.map +1 -1
- package/esm/lib/next-turn-params.test.js +72 -0
- package/esm/lib/next-turn-params.test.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 +21 -1
- 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-set-types.d.ts +298 -0
- package/esm/lib/tool-set-types.d.ts.map +1 -0
- package/esm/lib/tool-set-types.js +2 -0
- package/esm/lib/tool-set-types.js.map +1 -0
- package/esm/lib/tool-set.d.ts +157 -0
- package/esm/lib/tool-set.d.ts.map +1 -0
- package/esm/lib/tool-set.js +473 -0
- package/esm/lib/tool-set.js.map +1 -0
- 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 +667 -38
- 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 +227 -19
- package/esm/lib/tool.d.ts.map +1 -1
- package/esm/lib/tool.js +206 -6
- package/esm/lib/tool.js.map +1 -1
- package/esm/mcp/auth/auth-resolver.d.ts +14 -0
- package/esm/mcp/auth/auth-resolver.d.ts.map +1 -0
- package/esm/mcp/auth/auth-resolver.js +27 -0
- package/esm/mcp/auth/auth-resolver.js.map +1 -0
- package/esm/mcp/auth/auth-types.d.ts +33 -0
- package/esm/mcp/auth/auth-types.d.ts.map +1 -0
- package/esm/mcp/auth/auth-types.js +4 -0
- package/esm/mcp/auth/auth-types.js.map +1 -0
- package/esm/mcp/build-tools.d.ts +29 -0
- package/esm/mcp/build-tools.d.ts.map +1 -0
- package/esm/mcp/build-tools.js +80 -0
- package/esm/mcp/build-tools.js.map +1 -0
- package/esm/mcp/cache/cache-store.d.ts +24 -0
- package/esm/mcp/cache/cache-store.d.ts.map +1 -0
- package/esm/mcp/cache/cache-store.js +20 -0
- package/esm/mcp/cache/cache-store.js.map +1 -0
- package/esm/mcp/cache/cache-types.d.ts +71 -0
- package/esm/mcp/cache/cache-types.d.ts.map +1 -0
- package/esm/mcp/cache/cache-types.js +49 -0
- package/esm/mcp/cache/cache-types.js.map +1 -0
- package/esm/mcp/cache/serialize.d.ts +25 -0
- package/esm/mcp/cache/serialize.d.ts.map +1 -0
- package/esm/mcp/cache/serialize.js +96 -0
- package/esm/mcp/cache/serialize.js.map +1 -0
- package/esm/mcp/close-quietly.d.ts +24 -0
- package/esm/mcp/close-quietly.d.ts.map +1 -0
- package/esm/mcp/close-quietly.js +30 -0
- package/esm/mcp/close-quietly.js.map +1 -0
- package/esm/mcp/create-mcp-tools.d.ts +13 -0
- package/esm/mcp/create-mcp-tools.d.ts.map +1 -0
- package/esm/mcp/create-mcp-tools.js +87 -0
- package/esm/mcp/create-mcp-tools.js.map +1 -0
- package/esm/mcp/elicitation.d.ts +11 -0
- package/esm/mcp/elicitation.d.ts.map +1 -0
- package/esm/mcp/elicitation.js +51 -0
- package/esm/mcp/elicitation.js.map +1 -0
- package/esm/mcp/errors.d.ts +91 -0
- package/esm/mcp/errors.d.ts.map +1 -0
- package/esm/mcp/errors.js +91 -0
- package/esm/mcp/errors.js.map +1 -0
- package/esm/mcp/handle.d.ts +66 -0
- package/esm/mcp/handle.d.ts.map +1 -0
- package/esm/mcp/handle.js +323 -0
- package/esm/mcp/handle.js.map +1 -0
- package/esm/mcp/index.d.ts +13 -0
- package/esm/mcp/index.d.ts.map +1 -0
- package/esm/mcp/index.js +11 -0
- package/esm/mcp/index.js.map +1 -0
- package/esm/mcp/mcp-connection.d.ts +150 -0
- package/esm/mcp/mcp-connection.d.ts.map +1 -0
- package/esm/mcp/mcp-connection.js +478 -0
- package/esm/mcp/mcp-connection.js.map +1 -0
- package/esm/mcp/mcp-sdk.d.ts +10 -0
- package/esm/mcp/mcp-sdk.d.ts.map +1 -0
- package/esm/mcp/mcp-sdk.js +37 -0
- package/esm/mcp/mcp-sdk.js.map +1 -0
- package/esm/mcp/rehydrate.d.ts +76 -0
- package/esm/mcp/rehydrate.d.ts.map +1 -0
- package/esm/mcp/rehydrate.js +505 -0
- package/esm/mcp/rehydrate.js.map +1 -0
- package/esm/mcp/resource-tools.d.ts +17 -0
- package/esm/mcp/resource-tools.d.ts.map +1 -0
- package/esm/mcp/resource-tools.js +155 -0
- package/esm/mcp/resource-tools.js.map +1 -0
- package/esm/mcp/result-mapper.d.ts +19 -0
- package/esm/mcp/result-mapper.d.ts.map +1 -0
- package/esm/mcp/result-mapper.js +40 -0
- package/esm/mcp/result-mapper.js.map +1 -0
- package/esm/mcp/schema/json-schema-guards.d.ts +14 -0
- package/esm/mcp/schema/json-schema-guards.d.ts.map +1 -0
- package/esm/mcp/schema/json-schema-guards.js +19 -0
- package/esm/mcp/schema/json-schema-guards.js.map +1 -0
- package/esm/mcp/schema/json-schema-to-zod.d.ts +20 -0
- package/esm/mcp/schema/json-schema-to-zod.d.ts.map +1 -0
- package/esm/mcp/schema/json-schema-to-zod.js +72 -0
- package/esm/mcp/schema/json-schema-to-zod.js.map +1 -0
- package/esm/mcp/tool-wrapper.d.ts +41 -0
- package/esm/mcp/tool-wrapper.d.ts.map +1 -0
- package/esm/mcp/tool-wrapper.js +131 -0
- package/esm/mcp/tool-wrapper.js.map +1 -0
- package/esm/mcp/transport-types.d.ts +45 -0
- package/esm/mcp/transport-types.d.ts.map +1 -0
- package/esm/mcp/transport-types.js +2 -0
- package/esm/mcp/transport-types.js.map +1 -0
- package/esm/mcp/types.d.ts +119 -0
- package/esm/mcp/types.d.ts.map +1 -0
- package/esm/mcp/types.js +2 -0
- package/esm/mcp/types.js.map +1 -0
- package/esm/mcp/version.d.ts +3 -0
- package/esm/mcp/version.d.ts.map +1 -0
- package/esm/mcp/version.js +5 -0
- package/esm/mcp/version.js.map +1 -0
- package/package.json +70 -5
|
@@ -3,10 +3,12 @@ import type { RequestOptions } from '@openrouter/sdk/lib/sdks';
|
|
|
3
3
|
import type * as models from '@openrouter/sdk/models';
|
|
4
4
|
import type { $ZodObject, $ZodShape } from 'zod/v4/core';
|
|
5
5
|
import type { CallModelInput } from './async-params.js';
|
|
6
|
+
import type { DoomLoopOption, DoomLoopVerdict } from './doom-loop.js';
|
|
6
7
|
import type { HooksManager } from './hooks-manager.js';
|
|
8
|
+
import type { SessionUsageTotals } from './hooks-types.js';
|
|
7
9
|
import type { StreamableOutputItem } from './stream-transformers.js';
|
|
8
10
|
import type { ContextInput } from './tool-context.js';
|
|
9
|
-
import type { ConversationState,
|
|
11
|
+
import type { ConversationState, CorrelatedResponseStreamEvent, CorrelatedToolStreamEvent, ParsedToolCall, PendingAsyncTool, StateAccessor, StopWhen, Tool, ToolContextMapWithShared, TurnContext } from './tool-types.js';
|
|
10
12
|
/**
|
|
11
13
|
* Default directive appended as a final user message on the forced final
|
|
12
14
|
* turn (`allowFinalResponse` defaulting to on, or explicitly `true`).
|
|
@@ -56,6 +58,63 @@ export interface GetResponseOptions<TTools extends readonly Tool[], TShared exte
|
|
|
56
58
|
strictFinalResponse?: boolean;
|
|
57
59
|
/** Hook system for lifecycle events */
|
|
58
60
|
hooks?: HooksManager;
|
|
61
|
+
/**
|
|
62
|
+
* Doom-loop detection: catch runs that repeat the same tool call with
|
|
63
|
+
* identical arguments (including repeated empty or unparseable calls) or
|
|
64
|
+
* emit the same text over and over, and respond with a graduated ladder
|
|
65
|
+
* (observe → steer → block → stop). `true` enables recommended defaults;
|
|
66
|
+
* an object tunes thresholds. Off by default.
|
|
67
|
+
*/
|
|
68
|
+
doomLoop?: DoomLoopOption;
|
|
69
|
+
/**
|
|
70
|
+
* Cancel the whole run: stops the loop at the next boundary and aborts
|
|
71
|
+
* the in-flight API request/stream. See `CallModelInput.signal`.
|
|
72
|
+
*/
|
|
73
|
+
signal?: AbortSignal;
|
|
74
|
+
/**
|
|
75
|
+
* Default per-tool execution deadline in milliseconds. A tool-level
|
|
76
|
+
* `timeoutMs` overrides it. See `BaseToolFunction.timeoutMs` for the
|
|
77
|
+
* semantics (bounds the round's wait, not the tool body).
|
|
78
|
+
*/
|
|
79
|
+
toolTimeoutMs?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Concurrency limits for tool execution. A bare number is shorthand for
|
|
82
|
+
* `{ round: n }`. `round` bounds simultaneous executions within a tool
|
|
83
|
+
* round (unbounded by default — matching existing behavior); `background`
|
|
84
|
+
* bounds detached background-tool work (default 16).
|
|
85
|
+
*/
|
|
86
|
+
toolConcurrency?: number | {
|
|
87
|
+
round?: number;
|
|
88
|
+
background?: number;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Behavior for async tools (`tool.background` / `tool.deferred`).
|
|
92
|
+
*/
|
|
93
|
+
asyncTools?: {
|
|
94
|
+
/**
|
|
95
|
+
* What to do when the run would end with background tasks in flight:
|
|
96
|
+
* - `'drain'` (default): wait (bounded by `drainTimeoutMs`) and give the
|
|
97
|
+
* model up to `maxDrainTurns` extra turns so the final answer
|
|
98
|
+
* incorporates late results;
|
|
99
|
+
* - `'detach'`: return immediately; tasks keep running and their results
|
|
100
|
+
* are dropped (persisted as `orphaned` when a StateAccessor exists);
|
|
101
|
+
* - `'cancel'`: abort in-flight tasks and finish.
|
|
102
|
+
*/
|
|
103
|
+
onRunEnd?: 'drain' | 'detach' | 'cancel';
|
|
104
|
+
/** Bound on each end-of-run drain wait. Default 30_000. */
|
|
105
|
+
drainTimeoutMs?: number;
|
|
106
|
+
/** Max extra model turns granted to deliver drained results. Default 2. */
|
|
107
|
+
maxDrainTurns?: number;
|
|
108
|
+
/**
|
|
109
|
+
* Model-side check-ins: whether calling a long-running tool with a
|
|
110
|
+
* `taskId` answers status/logs/transcript instead of starting new work.
|
|
111
|
+
* Default true. When false, placeholders revert to "do not call this
|
|
112
|
+
* tool again" and the check schema is not advertised.
|
|
113
|
+
*/
|
|
114
|
+
checkins?: boolean;
|
|
115
|
+
/** Max characters for the `transcript` check view. Default 20_000. */
|
|
116
|
+
maxTranscriptChars?: number;
|
|
117
|
+
};
|
|
59
118
|
}
|
|
60
119
|
/**
|
|
61
120
|
* A wrapper around a streaming response that provides multiple consumption patterns.
|
|
@@ -87,8 +146,16 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
87
146
|
private toolEventBroadcaster;
|
|
88
147
|
private allToolExecutionRounds;
|
|
89
148
|
private resolvedRequest;
|
|
149
|
+
private activeConsumedForcedToolChoiceKey;
|
|
150
|
+
private configuredForcedToolChoiceKey;
|
|
151
|
+
private configuredToolChoice;
|
|
152
|
+
private preparedEffectiveToolChoice;
|
|
153
|
+
private preparedToolChoiceCommit;
|
|
154
|
+
private dispatchedToolChoiceCommit;
|
|
155
|
+
private dispatchedCallerToolChoice;
|
|
90
156
|
private pendingFreshItems;
|
|
91
157
|
private resumingFromClientTools;
|
|
158
|
+
private resumingFromAsyncTools;
|
|
92
159
|
private stateAccessor;
|
|
93
160
|
private currentState;
|
|
94
161
|
private requireApprovalFn;
|
|
@@ -105,7 +172,36 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
105
172
|
private readonly hookDeniedCalls;
|
|
106
173
|
private pendingModelCall;
|
|
107
174
|
private readonly sessionUsage;
|
|
175
|
+
private readonly doomLoopMonitor;
|
|
176
|
+
private doomLoopStop;
|
|
177
|
+
private pendingDoomLoopSteer;
|
|
178
|
+
private doomLoopRound;
|
|
179
|
+
private readonly doomLoopRoundDecisions;
|
|
180
|
+
private readonly doomLoopRoundKeyMaterial;
|
|
181
|
+
private doomLoopChain;
|
|
182
|
+
private pendingDoomLoopEscalation;
|
|
183
|
+
private readonly doomLoopEscalation;
|
|
184
|
+
private asyncToolRegistry;
|
|
185
|
+
private roundGate;
|
|
186
|
+
private backgroundPool;
|
|
187
|
+
private readonly perToolGates;
|
|
188
|
+
private readonly inflightToolControllers;
|
|
108
189
|
constructor(options: GetResponseOptions<TTools, TShared>);
|
|
190
|
+
/** Lazily create the async tool registry. */
|
|
191
|
+
private ensureAsyncToolRegistry;
|
|
192
|
+
/** Per-tool concurrency gate, created on first use. */
|
|
193
|
+
private perToolGate;
|
|
194
|
+
/**
|
|
195
|
+
* Compose the per-call abort signal for one tool execution: the run
|
|
196
|
+
* signal plus a per-call controller the engine can abort independently
|
|
197
|
+
* (timeout race, cancelTask, cancel()). The tool deadline deliberately
|
|
198
|
+
* does NOT ride the signal here — the race in `executeSingleToolCall`
|
|
199
|
+
* owns exactly one timer (which aborts the controller), so the round's
|
|
200
|
+
* timeout resolution and the body's abort observation cannot disagree.
|
|
201
|
+
*/
|
|
202
|
+
private composeToolSignal;
|
|
203
|
+
/** Abort every in-flight tool execution and background task. */
|
|
204
|
+
private abortAllToolWork;
|
|
109
205
|
/**
|
|
110
206
|
* Get or create the unified turn broadcaster (lazy initialization).
|
|
111
207
|
* Broadcasts all API stream events, tool events, and turn delimiters across turns.
|
|
@@ -249,6 +345,169 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
249
345
|
* `effectiveToolCall` reflects any `mutatedInput` piped by PreToolUse.
|
|
250
346
|
*/
|
|
251
347
|
private runToolWithHooks;
|
|
348
|
+
/**
|
|
349
|
+
* Advance the doom-loop round counter and declare the round's calls. Called
|
|
350
|
+
* at every execution-batch boundary (main tool round, auto-approve batch
|
|
351
|
+
* while pausing, approved-on-resume batch). Identical calls WITHIN one round
|
|
352
|
+
* are duplicates — one piece of loop evidence, one shared decision.
|
|
353
|
+
*
|
|
354
|
+
* `batch` is every call the round will make. A round's identity for one tool
|
|
355
|
+
* is the *set* of fingerprints it was called with, and that set has to be
|
|
356
|
+
* complete before any of the round's calls is scored: accumulating it call
|
|
357
|
+
* by call made a round that is a superset of the previous one transiently
|
|
358
|
+
* match it, blocking calls that represented real progress, with the outcome
|
|
359
|
+
* depending on emission order. Declaration is best-effort — a call whose key
|
|
360
|
+
* material is exempt or unhashable is simply left out, and the per-call
|
|
361
|
+
* fallback chain in `enqueueDoomLoopEvaluation` still governs identity at
|
|
362
|
+
* record time.
|
|
363
|
+
*
|
|
364
|
+
* Only calls that the round will actually CHECK are declared. A `loopKey`
|
|
365
|
+
* function is user code that may count or log, so it must not run for a call
|
|
366
|
+
* the detector never evaluates: manual tool calls (no `execute`, no
|
|
367
|
+
* `onToolCalled`) are handed to the caller and never recorded, and every
|
|
368
|
+
* execution path skips them with the same `isAutoResolvableTool` predicate
|
|
369
|
+
* used here. Their absence from the declared set is also correct on its own
|
|
370
|
+
* terms — they are not evidence, so they are not part of the round.
|
|
371
|
+
*
|
|
372
|
+
* INVARIANT — every declared member must eventually be recorded. A call
|
|
373
|
+
* that is declared but never reaches the doom-loop checkpoint is a phantom
|
|
374
|
+
* member of the round's identity: the tool's streak silently resets the
|
|
375
|
+
* moment the phantom stops being emitted (pinned by the "declared-but-
|
|
376
|
+
* never-recorded member" test in doom-loop-fanout.test.ts). The filters
|
|
377
|
+
* below therefore MIRROR every path that skips recording — unknown/manual
|
|
378
|
+
* tools (`isAutoResolvableTool`, also gating executeAutoApproveTools and
|
|
379
|
+
* the approval-resume loop), `hookDeniedCalls` (consumed before the
|
|
380
|
+
* checkpoint in executeSingleToolCall), and `loopKey`-exempt calls (early
|
|
381
|
+
* return in checkDoomLoopBeforeExecution; the same resolution is cached
|
|
382
|
+
* here so both sides agree). If you add a new short-circuit between
|
|
383
|
+
* declaration and the checkpoint — a pre-execution gate, a batch filter —
|
|
384
|
+
* it MUST be reflected here, or fan-out detection degrades silently for
|
|
385
|
+
* that tool rather than failing loudly.
|
|
386
|
+
*
|
|
387
|
+
* KNOWN RESIDUAL (documented, not mirrored): a per-request timeout or
|
|
388
|
+
* abort can cancel a call AFTER declaration but BEFORE its checkpoint,
|
|
389
|
+
* leaving a declared phantom for that round. Bounded and fail-safe: the
|
|
390
|
+
* round-set streak for that tool resets when the phantom stops recurring,
|
|
391
|
+
* while per-call streaks are unaffected — detection degrades to per-call
|
|
392
|
+
* for the affected tool rather than being lost. Un-declaring mid-round
|
|
393
|
+
* would mutate the round's identity while it is being scored, which is the
|
|
394
|
+
* incremental-set order-dependence this design exists to prevent.
|
|
395
|
+
*/
|
|
396
|
+
private beginDoomLoopRound;
|
|
397
|
+
/**
|
|
398
|
+
* Evaluate one call against the doom-loop monitor, serialized on
|
|
399
|
+
* `doomLoopChain` so parallel executions record in the deterministic
|
|
400
|
+
* order their evaluations were ENQUEUED (the `.map()` over a round's
|
|
401
|
+
* calls runs synchronously to its first await, i.e. model-emission
|
|
402
|
+
* order) — not in hash-completion order.
|
|
403
|
+
*
|
|
404
|
+
* Applies the fallback invariant: detection must never take down or
|
|
405
|
+
* alter a run except via its defined actions. Unhashable key material
|
|
406
|
+
* (bigint, non-finite numbers, circular/too-deep structures from a
|
|
407
|
+
* computed loopKey) degrades to `fallbackKeyMaterial`; if THAT is also
|
|
408
|
+
* unhashable (parse-error path passes null), detection is skipped for
|
|
409
|
+
* the call and it proceeds normally.
|
|
410
|
+
*
|
|
411
|
+
* Returns the final decision for this call (`action: 'proceed'` when no
|
|
412
|
+
* rung fired or the call is exempt/skipped; `message` carries the
|
|
413
|
+
* verdict's explanation for block outputs). Side effects (steer queue,
|
|
414
|
+
* stop arm) are applied here so every caller shares them.
|
|
415
|
+
*/
|
|
416
|
+
private enqueueDoomLoopEvaluation;
|
|
417
|
+
/**
|
|
418
|
+
* Doom-loop checkpoint for one tool call, run before PreToolUse/execution.
|
|
419
|
+
*
|
|
420
|
+
* Resolves the tool's `loopKey` declaration (function |
|
|
421
|
+
* false | absent — see {@link resolveLoopKeyMaterial}) and evaluates the
|
|
422
|
+
* call against the monitor. Ladder actions:
|
|
423
|
+
*
|
|
424
|
+
* - `observe` — hook already emitted, nothing else.
|
|
425
|
+
* - `steer` — queue a corrective user message for the next model turn.
|
|
426
|
+
* - `block` — return `blocked` so the caller synthesizes an error output
|
|
427
|
+
* without executing. The model sees the explanation in the tool result.
|
|
428
|
+
* - `stop` — arm `doomLoopStop`; ALSO blocks this call (executing a
|
|
429
|
+
* call the detector just condemned, then stopping, would be
|
|
430
|
+
* incoherent). The loop halts before the next model request.
|
|
431
|
+
*/
|
|
432
|
+
private checkDoomLoopBeforeExecution;
|
|
433
|
+
/**
|
|
434
|
+
* Emit the DoomLoopDetected hook for a verdict and resolve the final
|
|
435
|
+
* action. Handlers may override per event (`overrideAction`), letting
|
|
436
|
+
* userland escalate or de-escalate without reconfiguring the ladder;
|
|
437
|
+
* the LAST override in the chain wins (matching PermissionRequest's
|
|
438
|
+
* last-wins convention). Non-blockable verdicts (text, server tools —
|
|
439
|
+
* the tokens are already emitted / the tool already ran) downgrade a
|
|
440
|
+
* 'block' override to 'observe'. An 'escalate' override is honored only
|
|
441
|
+
* when an escalation mechanism is configured with remaining budget;
|
|
442
|
+
* otherwise it downgrades to 'observe' (never silently to a stronger
|
|
443
|
+
* action).
|
|
444
|
+
*/
|
|
445
|
+
private applyDoomLoopVerdict;
|
|
446
|
+
/**
|
|
447
|
+
* Apply the side effects of a resolved doom-loop action shared by every
|
|
448
|
+
* checkpoint: queue steer guidance, arm an escalation for the next
|
|
449
|
+
* request, or arm the stop verdict. 'block' has no shared side effect —
|
|
450
|
+
* the calling checkpoint synthesizes the blocked output itself.
|
|
451
|
+
*/
|
|
452
|
+
private applyDoomLoopSideEffects;
|
|
453
|
+
/**
|
|
454
|
+
* Consume a pending escalation into ONE-TURN request overrides:
|
|
455
|
+
*
|
|
456
|
+
* - `model` — replace the request's model for this dispatch only (the
|
|
457
|
+
* base `resolvedRequest` is never mutated, so the following turn
|
|
458
|
+
* reverts automatically).
|
|
459
|
+
* - `advisor` — append an `openrouter:advisor` server tool (transcript
|
|
460
|
+
* forwarded, instructions describing the detected loop) and pin
|
|
461
|
+
* `toolChoice` to it via `allowed_tools`/`required`, so the stuck model
|
|
462
|
+
* must consult the advisor before doing anything else this turn.
|
|
463
|
+
*
|
|
464
|
+
* Burns one unit of the escalation budget (persisted via the monitor) at
|
|
465
|
+
* APPLICATION time — verdicts the engine never got to apply (run stopped,
|
|
466
|
+
* paused, or overridden) do not spend.
|
|
467
|
+
*/
|
|
468
|
+
private takeDoomLoopEscalationOverrides;
|
|
469
|
+
/**
|
|
470
|
+
* Step-level doom-loop checkpoint on a fresh model response:
|
|
471
|
+
*
|
|
472
|
+
* 1. Text detectors — within-response token repetition + cross-step
|
|
473
|
+
* identical-text streak.
|
|
474
|
+
* 2. Server-tool fingerprints — server tools (web_search_call etc.) never
|
|
475
|
+
* pass through runToolWithHooks; their repetition is detected here,
|
|
476
|
+
* post-execution, from the echoed call fields on the output item.
|
|
477
|
+
* Post-execution ⇒ block is meaningless (allowBlock: false); the
|
|
478
|
+
* actions are observe/steer/stop.
|
|
479
|
+
*
|
|
480
|
+
* Runs where the loop already inspects each fresh response, so scripted
|
|
481
|
+
* transcripts exercise it deterministically.
|
|
482
|
+
*/
|
|
483
|
+
private checkDoomLoopForResponse;
|
|
484
|
+
/**
|
|
485
|
+
* Queue steer guidance, deduplicating identical messages (one verdict can
|
|
486
|
+
* repeat across calls in a round; the model needs the guidance once).
|
|
487
|
+
*/
|
|
488
|
+
private queueDoomLoopSteer;
|
|
489
|
+
/**
|
|
490
|
+
* Inject any queued doom-loop steer messages as a user message before the
|
|
491
|
+
* next model turn. Reuses the Stop-hook appendPrompt injection path, so
|
|
492
|
+
* state/messages advance observably. Called in-loop before each follow-up
|
|
493
|
+
* request AND before every pause-persist, so guidance queued right before
|
|
494
|
+
* a pause lands in the persisted conversation instead of being dropped.
|
|
495
|
+
*/
|
|
496
|
+
private flushDoomLoopSteer;
|
|
497
|
+
/**
|
|
498
|
+
* Halt the run for an armed doom-loop stop verdict, keeping the persisted
|
|
499
|
+
* history well-formed: every executable `function_call` in the current
|
|
500
|
+
* response that has no output yet gets a synthesized halt-error output
|
|
501
|
+
* (persisted via the normal tool-result path), so a stateful resume never
|
|
502
|
+
* sends a dangling `function_call` (providers reject those with a 400).
|
|
503
|
+
*
|
|
504
|
+
* Used by every stop site: the pre-round break, the post-round break, the
|
|
505
|
+
* no-tools early return, the allow-final-response gate, and the approval-
|
|
506
|
+
* resume gate.
|
|
507
|
+
*
|
|
508
|
+
* @param resolvedCallIds - call ids that already have outputs this round.
|
|
509
|
+
*/
|
|
510
|
+
private sealDoomLoopStop;
|
|
252
511
|
/**
|
|
253
512
|
* Build the per-emit context for lifecycle hook emits. Threads this run's
|
|
254
513
|
* session identity into `context.sessionId` on every emit, so a
|
|
@@ -258,14 +517,38 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
258
517
|
* run to start).
|
|
259
518
|
*/
|
|
260
519
|
private hookEmitContext;
|
|
520
|
+
/**
|
|
521
|
+
* Emit PostToolUse / PostToolUseFailure for an async task at settlement —
|
|
522
|
+
* the point its final output actually exists. Round-synchronous tools
|
|
523
|
+
* emit these in runToolWithHooks; async invocations skip that emission
|
|
524
|
+
* (no output yet) and land here instead. `durationMs` is the task's
|
|
525
|
+
* start-to-settle wall clock (includes background-pool queue wait, which
|
|
526
|
+
* counts against the task's own timeout budget). Observation-only — a
|
|
527
|
+
* throwing hook must not break delivery.
|
|
528
|
+
*/
|
|
529
|
+
private emitAsyncSettledHooks;
|
|
261
530
|
/**
|
|
262
531
|
* Emit SessionEnd exactly once, and only when a matching SessionStart
|
|
263
532
|
* actually succeeded. Safe to call from multiple teardown paths.
|
|
264
533
|
*/
|
|
265
534
|
private emitSessionEndOnce;
|
|
535
|
+
/**
|
|
536
|
+
* Materialize the running session aggregate as an immutable
|
|
537
|
+
* `SessionUsageTotals`. `cost` is present only when at least one response
|
|
538
|
+
* reported it, matching the hook payload's optionality (a summed cost of
|
|
539
|
+
* `0` from cost-less responses would read as "free" rather than "unknown").
|
|
540
|
+
*
|
|
541
|
+
* Single source of truth for both `SessionEnd.totalUsage` and the public
|
|
542
|
+
* `getUsage()` accessor, so the two can never drift.
|
|
543
|
+
*/
|
|
544
|
+
private snapshotSessionUsage;
|
|
266
545
|
/**
|
|
267
546
|
* Emit PostModelCall for a completed model response and fold its usage
|
|
268
547
|
* into the session aggregate. One emit per materialized response.
|
|
548
|
+
*
|
|
549
|
+
* Accumulation runs unconditionally — before the hooks short-circuit —
|
|
550
|
+
* because `getUsage()` surfaces these totals to callers who configured no
|
|
551
|
+
* hooks at all. Only the hook emit is gated on a `HooksManager`.
|
|
269
552
|
*/
|
|
270
553
|
private emitPostModelCall;
|
|
271
554
|
/**
|
|
@@ -292,9 +575,9 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
292
575
|
private runStopHook;
|
|
293
576
|
/**
|
|
294
577
|
* Session teardown for the no-tools stream paths, which bypass
|
|
295
|
-
* executeToolsIfNeeded (the normal SessionEnd site).
|
|
296
|
-
*
|
|
297
|
-
* stream's own outcome.
|
|
578
|
+
* executeToolsIfNeeded (the normal SessionEnd site). Materializes the
|
|
579
|
+
* parked model-call telemetry, emits SessionEnd once, and drains pending
|
|
580
|
+
* hook work. Never throws: teardown must not mask the stream's own outcome.
|
|
298
581
|
*/
|
|
299
582
|
private finishHooksSessionForStream;
|
|
300
583
|
/**
|
|
@@ -332,6 +615,19 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
332
615
|
* one-time `console.warn` in dev builds explains why).
|
|
333
616
|
*/
|
|
334
617
|
private maybeRunUserPromptSubmit;
|
|
618
|
+
/**
|
|
619
|
+
* Settle an async invocation on the approval/resume path, where results
|
|
620
|
+
* are accumulated as `UnsentToolResult`s rather than round outputs.
|
|
621
|
+
*
|
|
622
|
+
* - background: degrade to synchronous execution — the approval flow is
|
|
623
|
+
* already a discrete resume step, so there is no round to unblock; the
|
|
624
|
+
* work is awaited and its (validated) value becomes the unsent result.
|
|
625
|
+
* - defer: the external task HAS been started by `start()`; record the
|
|
626
|
+
* pending placeholder as the unsent result and track the task on
|
|
627
|
+
* `pendingAsyncTools` so `resumeToolResults` can deliver the real
|
|
628
|
+
* result later as an envelope.
|
|
629
|
+
*/
|
|
630
|
+
private settleAsyncInvocationAsUnsent;
|
|
335
631
|
/**
|
|
336
632
|
* Execute tools that can auto-execute (don't require approval) in parallel.
|
|
337
633
|
*
|
|
@@ -392,6 +688,31 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
392
688
|
* @param unresolvedCalls - Manual (or otherwise non-auto-resolvable) tool calls
|
|
393
689
|
*/
|
|
394
690
|
private persistClientToolsPause;
|
|
691
|
+
/**
|
|
692
|
+
* Persist state when one or more deferred tools (`tool.deferred`) started
|
|
693
|
+
* durable external tasks this round. The placeholder outputs are already
|
|
694
|
+
* persisted (the round is fully paired), so — unlike HITL/manual pauses —
|
|
695
|
+
* nothing dangles; the pause exists purely because the model's next turn
|
|
696
|
+
* should wait for the real results.
|
|
697
|
+
*
|
|
698
|
+
* Mirrors `persistHitlPause` in shape. Status `awaiting_async_tools` is
|
|
699
|
+
* distinct from `awaiting_client_tools`: the tool DID execute; only its
|
|
700
|
+
* result is pending. Resumption happens via the deferred tool's
|
|
701
|
+
* `.resolve()` / `.fail()` / `.cancel()` or `resumeToolResults()`.
|
|
702
|
+
*/
|
|
703
|
+
private persistAsyncToolPause;
|
|
704
|
+
/**
|
|
705
|
+
* Harvest background tasks that settled since the last flush and inject
|
|
706
|
+
* their outcomes into the conversation as `tool_task_result` envelope
|
|
707
|
+
* messages (user role). Runs at the same safe point as
|
|
708
|
+
* `flushDoomLoopSteer()` — after a fully-paired round, before the next
|
|
709
|
+
* request — because injecting between a dangling `function_call` and its
|
|
710
|
+
* future output would be invalid history.
|
|
711
|
+
*
|
|
712
|
+
* A delivery is forward progress: the text-repetition streak is reset so a
|
|
713
|
+
* model saying "still waiting…" between deliveries is not condemned.
|
|
714
|
+
*/
|
|
715
|
+
private flushAsyncToolDeliveries;
|
|
395
716
|
/**
|
|
396
717
|
* Compute the `output` payload sent to the model for a successfully
|
|
397
718
|
* settled tool execution. Routes through `toModelOutput` when the tool
|
|
@@ -419,7 +740,119 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
419
740
|
* outcome consumed by `executeToolRound`'s aggregation loop.
|
|
420
741
|
*/
|
|
421
742
|
private executeSingleToolCall;
|
|
743
|
+
/**
|
|
744
|
+
* The `tool_timeout` execution outcome for a call whose deadline fired:
|
|
745
|
+
* the model receives an error output immediately (the abandoned body
|
|
746
|
+
* keeps running detached).
|
|
747
|
+
*/
|
|
748
|
+
private buildToolTimeoutOutcome;
|
|
749
|
+
/**
|
|
750
|
+
* PermissionRequest-hook denial for a call, as a `hook_blocked` outcome —
|
|
751
|
+
* or null when the call was not denied. Consumes the entry so a later
|
|
752
|
+
* round with a reused id is not affected.
|
|
753
|
+
*/
|
|
754
|
+
private consumeHookDenial;
|
|
755
|
+
/**
|
|
756
|
+
* Race a tool execution against its deadline. Returns `'timeout'` when the
|
|
757
|
+
* deadline fires first — the caller synthesizes the `tool_timeout` output —
|
|
758
|
+
* and aborts the body's controller so cooperative tools stop working. The
|
|
759
|
+
* timer is cleared on settle either way.
|
|
760
|
+
*/
|
|
761
|
+
private raceToolDeadline;
|
|
422
762
|
private executeToolRound;
|
|
763
|
+
/**
|
|
764
|
+
* Handle one async invocation from a round.
|
|
765
|
+
*
|
|
766
|
+
* Background: start the work under the background pool, race it against
|
|
767
|
+
* the grace window — settle in time and the call yields a plain
|
|
768
|
+
* synchronous output (no async machinery visible); otherwise emit the
|
|
769
|
+
* pending placeholder and let the registry track the task.
|
|
770
|
+
*
|
|
771
|
+
* Deferred: emit the placeholder and report the task for the pause path.
|
|
772
|
+
*/
|
|
773
|
+
private handleAsyncInvocation;
|
|
774
|
+
/**
|
|
775
|
+
* Reject a `ctx.defer()` taskId already claimed by ANOTHER pending task.
|
|
776
|
+
* Task ids are the resolution key (resumeToolResults matches on them), so
|
|
777
|
+
* a duplicate would make one of the two calls unresolvable — the
|
|
778
|
+
* conversation would wait forever. Caller-supplied ids
|
|
779
|
+
* (`ctx.defer(ticket.id)`) carry no uniqueness guarantee; reject the
|
|
780
|
+
* collision at start time, when the tool can still see it.
|
|
781
|
+
* @returns the error output for the colliding call, or null when unique.
|
|
782
|
+
*/
|
|
783
|
+
private rejectDuplicateTaskId;
|
|
784
|
+
/**
|
|
785
|
+
* A mutable binding between a tool call's run context and its (created
|
|
786
|
+
* later) ToolTask. `ctx.log()` entries before the task exists are
|
|
787
|
+
* buffered; `ctx.onMessage()` registrations are forwarded on bind.
|
|
788
|
+
*/
|
|
789
|
+
private createRunBinding;
|
|
790
|
+
/**
|
|
791
|
+
* Run a background tool's work under the tool's own concurrency gate AND
|
|
792
|
+
* the background pool. The round path does not touch the per-tool gate
|
|
793
|
+
* for background lifecycles (the thunk had not run yet), so the gate is
|
|
794
|
+
* acquired here for the body's lifetime — `maxConcurrency` bounds
|
|
795
|
+
* executions of the BODY, wherever they run.
|
|
796
|
+
*
|
|
797
|
+
* Acquisition order: per-tool gate FIRST, then the pool. The narrow gate
|
|
798
|
+
* is where a saturated tool's excess calls queue — queuing them on the
|
|
799
|
+
* shared pool instead would let 16 waiters on one maxConcurrency:2 tool
|
|
800
|
+
* hold every global slot while only two bodies run, starving every other
|
|
801
|
+
* background tool. Order is deadlock-safe: only background bodies take
|
|
802
|
+
* the per-tool gate (the round path skips it for them), and every body
|
|
803
|
+
* acquires in this same fixed order.
|
|
804
|
+
*
|
|
805
|
+
* Queue wait counts against the task's own timeout (tracked by the
|
|
806
|
+
* registry); an abort observed before the slots free up short-circuits
|
|
807
|
+
* without running the body.
|
|
808
|
+
*/
|
|
809
|
+
private runBackgroundWork;
|
|
810
|
+
/** Broadcast a `tool.async_started` event on the turn broadcaster. */
|
|
811
|
+
private broadcastAsyncStarted;
|
|
812
|
+
/** Broadcast a `tool.async_settled` event on the turn broadcaster. */
|
|
813
|
+
private broadcastAsyncSettled;
|
|
814
|
+
/**
|
|
815
|
+
* Build the pending placeholder `function_call_output` for an async tool
|
|
816
|
+
* call. Pairs the `function_call` immediately (providers 400 on unpaired
|
|
817
|
+
* calls in follow-up history); the real result arrives later as a
|
|
818
|
+
* `tool_task_result` envelope. The note steers the model: with check-ins
|
|
819
|
+
* enabled it points at the universal `task` tool; with check-ins
|
|
820
|
+
* disabled it forbids re-calling entirely.
|
|
821
|
+
*/
|
|
822
|
+
private buildPendingPlaceholder;
|
|
823
|
+
/**
|
|
824
|
+
* True when the built-in task tool is active for this run. Mirrors
|
|
825
|
+
* `needsTaskTool` exactly: when a user tool claims the reserved name the
|
|
826
|
+
* built-in is not registered, so calls named "task" must NOT be
|
|
827
|
+
* intercepted — they belong to the user's tool.
|
|
828
|
+
*/
|
|
829
|
+
private taskToolActive;
|
|
830
|
+
/**
|
|
831
|
+
* Answer a call to the universal `task` tool: resolve the taskId to its
|
|
832
|
+
* live registry task (or persisted pendingAsyncTools entry, post-restart)
|
|
833
|
+
* and its OWNING tool, then dispatch by action:
|
|
834
|
+
*
|
|
835
|
+
* - `check` (default): the owning tool's `check.execute` when declared
|
|
836
|
+
* (custom `params` validated against `check.schema`), else the SDK
|
|
837
|
+
* default status / logs / transcript views.
|
|
838
|
+
* - `steer`: deliver `message` to the run body's inbox.
|
|
839
|
+
* - `result`: the final result if settled, else the status view.
|
|
840
|
+
* - `cancel`: cancel the task.
|
|
841
|
+
*/
|
|
842
|
+
private answerTaskToolCall;
|
|
843
|
+
/** `task` tool, action=cancel. */
|
|
844
|
+
private taskToolCancel;
|
|
845
|
+
/** `task` tool, action=steer. Returns [result, error?] for `answer`. */
|
|
846
|
+
private taskToolSteer;
|
|
847
|
+
/**
|
|
848
|
+
* `task` tool, action=check (and the status-view fallback for `result`
|
|
849
|
+
* on an unsettled task): the owning tool's `check.execute` when
|
|
850
|
+
* declared, else the SDK default views — live-task-backed when the task
|
|
851
|
+
* is in this process, persisted-state-backed post-restart.
|
|
852
|
+
*/
|
|
853
|
+
private taskToolCheck;
|
|
854
|
+
/** Build the narrow ToolTaskHandle façade for check.execute handlers. */
|
|
855
|
+
private buildTaskHandle;
|
|
423
856
|
/**
|
|
424
857
|
* Resolve async functions for the current turn.
|
|
425
858
|
* Updates the resolved request with turn-specific parameter values.
|
|
@@ -439,6 +872,48 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
439
872
|
* Uses streaming and pipes events through the turn broadcaster when available.
|
|
440
873
|
*/
|
|
441
874
|
private makeFollowupRequest;
|
|
875
|
+
/**
|
|
876
|
+
* Append a response's output items to the accumulated request input,
|
|
877
|
+
* exactly as `makeFollowupRequest` does before adding tool results. Used
|
|
878
|
+
* by the end-of-run drain, where envelope messages must land AFTER the
|
|
879
|
+
* final assistant output in the request.
|
|
880
|
+
*/
|
|
881
|
+
private accumulateResponseIntoInput;
|
|
882
|
+
/**
|
|
883
|
+
* End-of-run handling for async background tasks still in flight (or
|
|
884
|
+
* settled but unharvested) when the loop is about to finalize.
|
|
885
|
+
*
|
|
886
|
+
* - `'drain'` (default): wait for tasks (bounded by `drainTimeoutMs`),
|
|
887
|
+
* inject their results, and give the model up to `maxDrainTurns` extra
|
|
888
|
+
* `toolChoice: 'none'` turns so the final answer incorporates them.
|
|
889
|
+
* - `'detach'`: return immediately; still-working tasks are marked
|
|
890
|
+
* `orphaned` on persisted state and their eventual results are dropped.
|
|
891
|
+
* - `'cancel'`: abort in-flight tasks and finish.
|
|
892
|
+
*
|
|
893
|
+
* @returns the (possibly replaced) final response.
|
|
894
|
+
*/
|
|
895
|
+
private handleRunEndAsyncTasks;
|
|
896
|
+
/**
|
|
897
|
+
* Broadcast every unharvested settlement as dropped (no in-run delivery),
|
|
898
|
+
* mirror the terminal status onto persisted `pendingAsyncTools`, and
|
|
899
|
+
* append a terminal `tool_task_result` envelope to persisted history.
|
|
900
|
+
*
|
|
901
|
+
* The status mirror prevents a stuck resume: an entry persisted as
|
|
902
|
+
* `working` for a task that only ever settles in-memory (abortAll at
|
|
903
|
+
* drain exhaustion / run cancel) would pin a later cross-process resume
|
|
904
|
+
* to `awaiting_async_tools` forever. The envelope keeps the pending
|
|
905
|
+
* placeholder's "the result will be delivered" promise honest for
|
|
906
|
+
* PERSISTED conversations: the run is over (no more model dispatch), but
|
|
907
|
+
* the terminal outcome rides along on the next `callModel({ state })`
|
|
908
|
+
* instead of vanishing.
|
|
909
|
+
*/
|
|
910
|
+
private dropSettledTasks;
|
|
911
|
+
/**
|
|
912
|
+
* `onRunEnd: 'detach'`: mark still-working tasks orphaned on persisted
|
|
913
|
+
* state (their eventual results are dropped) and drop unharvested
|
|
914
|
+
* settlements.
|
|
915
|
+
*/
|
|
916
|
+
private detachWorkingTasks;
|
|
442
917
|
/**
|
|
443
918
|
* Make a final no-tool-calls request to coerce a text response after the
|
|
444
919
|
* loop was halted by `stopWhen` mid-tool-call. Reuses the resolved request
|
|
@@ -453,6 +928,17 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
453
928
|
/**
|
|
454
929
|
* Validate the final response has required fields.
|
|
455
930
|
*
|
|
931
|
+
* Message prefixes (`Invalid final response: missing required fields`,
|
|
932
|
+
* `Invalid final response: empty or invalid output`) are load-bearing —
|
|
933
|
+
* callers and tests match on them. Detail is appended after an em dash;
|
|
934
|
+
* never reword the prefixes.
|
|
935
|
+
*
|
|
936
|
+
* The detail exists because the bare "empty or invalid output" message was
|
|
937
|
+
* routinely misread as "the model returned a tool call and validation
|
|
938
|
+
* rejected it" (issue #45). It does not: this is a pure array-length check,
|
|
939
|
+
* so a `function_call`-only output passes. Naming the actual defect —
|
|
940
|
+
* empty array vs. wrong type — stops that misdiagnosis at the error string.
|
|
941
|
+
*
|
|
456
942
|
* @param response - The response to validate
|
|
457
943
|
* @param allowEmptyOutput - When true, tolerate an empty (but present) output array
|
|
458
944
|
* @throws Error if response is missing required fields or has invalid output
|
|
@@ -471,6 +957,22 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
471
957
|
* prefix intact.
|
|
472
958
|
*/
|
|
473
959
|
private retryCurrentRequest;
|
|
960
|
+
/**
|
|
961
|
+
* Build the RequestOptions for one API dispatch, composing the run-level
|
|
962
|
+
* cancellation signal (`options.signal`) with the caller's RequestOptions.
|
|
963
|
+
*
|
|
964
|
+
* The SDK only auto-wires `timeoutMs` into an abort signal when NO signal
|
|
965
|
+
* is present on the options — a supplied signal silently disables the
|
|
966
|
+
* timeout. Composing here keeps both: each request is bounded by
|
|
967
|
+
* whichever of {run signal, caller signal, per-request timeout} fires
|
|
968
|
+
* first. MUST be called once per dispatch — `AbortSignal.timeout()`
|
|
969
|
+
* starts counting at creation, so a cached composite would burn its
|
|
970
|
+
* budget across turns instead of per request.
|
|
971
|
+
*
|
|
972
|
+
* Also the per-request fail-fast checkpoint: an already-aborted run
|
|
973
|
+
* signal throws its abort reason here, before any network I/O.
|
|
974
|
+
*/
|
|
975
|
+
private dispatchRequestOptions;
|
|
474
976
|
/**
|
|
475
977
|
* Resolve async functions in the request for a given turn context.
|
|
476
978
|
* Extracts non-function fields and resolves any async parameter functions.
|
|
@@ -479,6 +981,33 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
479
981
|
* @returns The resolved request without async functions
|
|
480
982
|
*/
|
|
481
983
|
private resolveRequestForContext;
|
|
984
|
+
/**
|
|
985
|
+
* Record the freshly-resolved caller choice before deriving its wire value.
|
|
986
|
+
* An unforced turn clears the consumed key, re-arming the same forced value
|
|
987
|
+
* if it appears again later. A different forced key re-arms immediately.
|
|
988
|
+
*/
|
|
989
|
+
private applyResolvedForcedToolChoicePolicy;
|
|
990
|
+
/**
|
|
991
|
+
* Derive the caller-configured tool choice for the active logical run.
|
|
992
|
+
* Engine-owned one-turn overrides (`none` for finalization, or a pinned
|
|
993
|
+
* doom-loop advisor) are applied after this helper and therefore still win.
|
|
994
|
+
*/
|
|
995
|
+
private applyForcedToolChoicePolicy;
|
|
996
|
+
/**
|
|
997
|
+
* Bind the prepared caller-policy transition to the actual wire request.
|
|
998
|
+
* Engine-owned overrides (for example a forced advisor consult) deliberately
|
|
999
|
+
* produce a different semantic key and therefore cannot commit caller state.
|
|
1000
|
+
*/
|
|
1001
|
+
private beginToolChoiceDispatch;
|
|
1002
|
+
/**
|
|
1003
|
+
* Commit only after the prepared caller choice reached the wire and a
|
|
1004
|
+
* response was materialized. A pause before dispatch leaves persisted state
|
|
1005
|
+
* untouched; a forced choice is consumed only when that response called a
|
|
1006
|
+
* tool.
|
|
1007
|
+
*/
|
|
1008
|
+
private commitToolChoiceDispatch;
|
|
1009
|
+
/** Clear all runtime and persisted policy state for a terminal run exit. */
|
|
1010
|
+
private resetForcedToolChoicePolicy;
|
|
482
1011
|
/**
|
|
483
1012
|
* Apply `onResponseReceived` hooks to the freshly-supplied input items
|
|
484
1013
|
* only, without re-hooking historical items that live in
|
|
@@ -549,6 +1078,10 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
549
1078
|
* Get the complete response object including usage information.
|
|
550
1079
|
* This will consume the stream until completion and execute any tools.
|
|
551
1080
|
* Returns the full OpenResponsesResult with usage data (inputTokens, outputTokens, cachedTokens, etc.)
|
|
1081
|
+
*
|
|
1082
|
+
* Note: in a multi-round tool loop this is the **final** round's response
|
|
1083
|
+
* only — its `usage` block covers that one generation, not the run. For
|
|
1084
|
+
* aggregate totals across every model call, use `getUsage()`.
|
|
552
1085
|
*/
|
|
553
1086
|
getResponse(): Promise<models.OpenResponsesResult>;
|
|
554
1087
|
/**
|
|
@@ -556,7 +1089,7 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
556
1089
|
* Multiple consumers can iterate over this stream concurrently.
|
|
557
1090
|
* Includes API events, tool events, and turn.start/turn.end delimiters.
|
|
558
1091
|
*/
|
|
559
|
-
getFullResponsesStream(): AsyncIterableIterator<
|
|
1092
|
+
getFullResponsesStream(): AsyncIterableIterator<CorrelatedResponseStreamEvent<TTools>>;
|
|
560
1093
|
/**
|
|
561
1094
|
* Stream only text deltas as they arrive from all turns.
|
|
562
1095
|
* This filters the full event stream to only yield text content,
|
|
@@ -576,6 +1109,17 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
576
1109
|
* - file_search_call: File search operations
|
|
577
1110
|
* - image_generation_call: Image generation operations
|
|
578
1111
|
* - function_call_output: Results from executed tools
|
|
1112
|
+
*
|
|
1113
|
+
* This stream carries **output items only** — no usage or response-level
|
|
1114
|
+
* metadata. The `response.completed` events (which hold each round's usage
|
|
1115
|
+
* block) are not surfaced here, and `getResponse()` afterwards returns only
|
|
1116
|
+
* the *final* round's response, so a multi-round tool loop's `tool_calls`
|
|
1117
|
+
* generations are not accounted for by either. For usage, reach for:
|
|
1118
|
+
* - `getUsage()` — aggregate token/cost totals across every round
|
|
1119
|
+
* - the `PostModelCall` hook — one emit per model call, with
|
|
1120
|
+
* `turnType`/`turnNumber` and that call's own `usage`
|
|
1121
|
+
* - `getFullResponsesStream()` — raw events including each round's
|
|
1122
|
+
* `response.completed`, and therefore its per-round usage block
|
|
579
1123
|
*/
|
|
580
1124
|
getItemsStream(): AsyncIterableIterator<StreamableOutputItem<TTools>>;
|
|
581
1125
|
/**
|
|
@@ -620,7 +1164,7 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
620
1164
|
* - Tool call argument deltas as { type: "delta", content: string }
|
|
621
1165
|
* - Preliminary results as { type: "preliminary_result", toolCallId, result }
|
|
622
1166
|
*/
|
|
623
|
-
getToolStream(): AsyncIterableIterator<
|
|
1167
|
+
getToolStream(): AsyncIterableIterator<CorrelatedToolStreamEvent<TTools>>;
|
|
624
1168
|
/**
|
|
625
1169
|
* Get all tool calls from the completed response (before auto-execution).
|
|
626
1170
|
* Note: If tools have execute functions, they will be automatically executed
|
|
@@ -647,16 +1191,66 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
647
1191
|
*/
|
|
648
1192
|
getContextUpdates(): AsyncGenerator<ToolContextMapWithShared<TTools, TShared>>;
|
|
649
1193
|
/**
|
|
650
|
-
* Cancel the underlying stream and all consumers
|
|
1194
|
+
* Cancel the underlying stream and all consumers, and abort in-flight
|
|
1195
|
+
* tool executions and background tasks (their `ctx.signal` fires).
|
|
1196
|
+
*
|
|
1197
|
+
* Note: prior releases only cancelled the stream; aborting tool work is
|
|
1198
|
+
* new with async tool support.
|
|
651
1199
|
*/
|
|
652
1200
|
cancel(): Promise<void>;
|
|
1201
|
+
/**
|
|
1202
|
+
* Snapshot of every async tool task tracked by this run (background and
|
|
1203
|
+
* deferred), including settled ones.
|
|
1204
|
+
*/
|
|
1205
|
+
getAsyncTasks(): PendingAsyncTool[];
|
|
1206
|
+
/**
|
|
1207
|
+
* Cancel one in-process async task by its task id. Background tasks abort
|
|
1208
|
+
* their `ctx.signal`; the model receives a `status: 'cancelled'` envelope
|
|
1209
|
+
* at the next delivery point so it is not left waiting forever.
|
|
1210
|
+
*
|
|
1211
|
+
* For deferred tasks in a paused (possibly other-process) conversation,
|
|
1212
|
+
* use the deferred tool's `.cancel()` method instead.
|
|
1213
|
+
*
|
|
1214
|
+
* @returns true when a working task was found and cancelled.
|
|
1215
|
+
*/
|
|
1216
|
+
cancelTask(taskId: string, reason?: string): boolean;
|
|
1217
|
+
/**
|
|
1218
|
+
* Send a steering message to a running in-process task. Delivered to the
|
|
1219
|
+
* run body's `ctx.onMessage` handler (queued until one registers). Agent
|
|
1220
|
+
* tools (`tool.agent()`) auto-forward messages into the child
|
|
1221
|
+
* conversation as user messages at the child's next turn boundary.
|
|
1222
|
+
*
|
|
1223
|
+
* Throws for deferred tasks — their work runs in an external system;
|
|
1224
|
+
* steer it there.
|
|
1225
|
+
*
|
|
1226
|
+
* @returns true when a working task received (or queued) the message.
|
|
1227
|
+
*/
|
|
1228
|
+
sendToTask(taskId: string, message: unknown): boolean;
|
|
1229
|
+
/**
|
|
1230
|
+
* Queue a user-role message for injection into this run's conversation at
|
|
1231
|
+
* the next safe turn boundary (the same mechanism doom-loop steer
|
|
1232
|
+
* guidance uses — never between a dangling `function_call` and its
|
|
1233
|
+
* output). The primary consumer is `tool.agent()`, which forwards
|
|
1234
|
+
* steering messages into child conversations through this.
|
|
1235
|
+
*
|
|
1236
|
+
* Delivery requires a next dispatch: boundaries are post-tool-round
|
|
1237
|
+
* follow-ups, pause-persists, and end-of-run drain turns. A message
|
|
1238
|
+
* queued while the run is composing its FINAL text response (no further
|
|
1239
|
+
* dispatch of any kind) has no boundary left and is not delivered —
|
|
1240
|
+
* steer while the run still has work in flight.
|
|
1241
|
+
*/
|
|
1242
|
+
queueUserMessage(text: string): void;
|
|
653
1243
|
/**
|
|
654
1244
|
* Check if the conversation requires human/client input to continue.
|
|
655
|
-
* Returns true when the conversation is paused waiting
|
|
656
|
-
*
|
|
657
|
-
* (`awaiting_hitl`),
|
|
658
|
-
*
|
|
659
|
-
*
|
|
1245
|
+
* Returns true when the conversation is paused waiting on the CALLER —
|
|
1246
|
+
* approval/rejection (`awaiting_approval`), HITL tool resume
|
|
1247
|
+
* (`awaiting_hitl`), client-executed manual tools
|
|
1248
|
+
* (`awaiting_client_tools`), or an external async task resolution
|
|
1249
|
+
* (`awaiting_async_tools`, cleared via the deferred tool's `.resolve()` /
|
|
1250
|
+
* `.fail()` / `.cancel()` or `resumeToolResults()` — NOT via
|
|
1251
|
+
* `approveToolCalls`). Also returns true whenever `pendingToolCalls` is
|
|
1252
|
+
* populated regardless of status. To branch on the pause KIND, read
|
|
1253
|
+
* `(await getState()).status` instead.
|
|
660
1254
|
*/
|
|
661
1255
|
requiresApproval(): Promise<boolean>;
|
|
662
1256
|
/**
|
|
@@ -671,5 +1265,47 @@ export declare class ModelResult<TTools extends readonly Tool[], TShared extends
|
|
|
671
1265
|
* To resume a conversation, use the StateAccessor pattern.
|
|
672
1266
|
*/
|
|
673
1267
|
getState(): Promise<ConversationState<TTools>>;
|
|
1268
|
+
/**
|
|
1269
|
+
* The doom-loop verdict that stopped this run, or null when the run was
|
|
1270
|
+
* not stopped by doom-loop detection (including when detection is off).
|
|
1271
|
+
* Resolves after the tool loop settles. For per-event observation use the
|
|
1272
|
+
* `DoomLoopDetected` hook.
|
|
1273
|
+
*/
|
|
1274
|
+
getDoomLoopVerdict(): Promise<DoomLoopVerdict | null>;
|
|
1275
|
+
/**
|
|
1276
|
+
* Aggregate token/cost usage across **every** model call this run made —
|
|
1277
|
+
* the initial request, each tool-round follow-up, the empty-final retry,
|
|
1278
|
+
* the `allowFinalResponse` final turn, and approval-resume requests.
|
|
1279
|
+
*
|
|
1280
|
+
* This is the pull-based counterpart to the `SessionEnd.totalUsage` hook
|
|
1281
|
+
* payload (same `SessionUsageTotals` shape, same numbers) and exists
|
|
1282
|
+
* because `getResponse()` returns only the **final** round's response: in a
|
|
1283
|
+
* multi-round tool loop the `tool_calls` generations' tokens are otherwise
|
|
1284
|
+
* unreachable, and `getItemsStream()` carries output items only — no
|
|
1285
|
+
* `response.completed` usage block.
|
|
1286
|
+
*
|
|
1287
|
+
* Gates on run completion the same way `getResponse()` does, so totals are
|
|
1288
|
+
* final whether you await it directly, after `getResponse()`, or after
|
|
1289
|
+
* consuming any of the streaming getters — except on an approval-resumed
|
|
1290
|
+
* run, where reading usage never advances the tool loop: the resume
|
|
1291
|
+
* request's own generation is awaited and counted, but no further rounds
|
|
1292
|
+
* are driven, so totals can be mid-run. Await `getResponse()`/`getText()`
|
|
1293
|
+
* first for final totals there. On a run that paused (approval / HITL) or
|
|
1294
|
+
* failed, it returns the totals accrued so far — `modelCalls: 0` with
|
|
1295
|
+
* zeroed tokens when no model call ever completed.
|
|
1296
|
+
*
|
|
1297
|
+
* Unlike `getResponse()`, this never rejects: a failed run still consumed
|
|
1298
|
+
* tokens, and cost accounting typically runs in a `finally`/`catch` where a
|
|
1299
|
+
* second throw would mask the run's original error. Await the run itself
|
|
1300
|
+
* (e.g. `getResponse()`) if you need to observe the failure.
|
|
1301
|
+
*
|
|
1302
|
+
* `cost` is present only when the server reported cost accounting for at
|
|
1303
|
+
* least one call; a `0` would be indistinguishable from "free".
|
|
1304
|
+
*
|
|
1305
|
+
* For per-call granularity use the `PostModelCall` hook (one emit per model
|
|
1306
|
+
* call, with `turnType`/`turnNumber`) or `getFullResponsesStream()`, whose
|
|
1307
|
+
* `response.completed` events carry each round's own usage block.
|
|
1308
|
+
*/
|
|
1309
|
+
getUsage(): Promise<SessionUsageTotals>;
|
|
674
1310
|
}
|
|
675
1311
|
//# sourceMappingURL=model-result.d.ts.map
|