@graphorin/agent 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/README.md +31 -11
- package/dist/errors/index.d.ts +17 -4
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +19 -3
- package/dist/errors/index.js.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +153 -1930
- package/dist/factory.js.map +1 -1
- package/dist/fanout/index.d.ts +13 -1
- package/dist/fanout/index.d.ts.map +1 -1
- package/dist/fanout/index.js +13 -4
- package/dist/fanout/index.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -9
- package/dist/index.js.map +1 -1
- package/dist/lateral-leak/index.js +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/run-state/index.d.ts +32 -6
- package/dist/run-state/index.d.ts.map +1 -1
- package/dist/run-state/index.js +46 -22
- package/dist/run-state/index.js.map +1 -1
- package/dist/runtime/agent-surface.js +122 -0
- package/dist/runtime/agent-surface.js.map +1 -0
- package/dist/runtime/agent-to-tool.d.ts +51 -0
- package/dist/runtime/agent-to-tool.d.ts.map +1 -0
- package/dist/runtime/agent-to-tool.js +145 -0
- package/dist/runtime/agent-to-tool.js.map +1 -0
- package/dist/runtime/approvals.js +0 -0
- package/dist/runtime/approvals.js.map +1 -0
- package/dist/runtime/dispatch.js +108 -0
- package/dist/runtime/dispatch.js.map +1 -0
- package/dist/runtime/executor-wiring.js +128 -0
- package/dist/runtime/executor-wiring.js.map +1 -0
- package/dist/runtime/fallback-chain.js +139 -0
- package/dist/runtime/fallback-chain.js.map +1 -0
- package/dist/runtime/handoff.js +307 -0
- package/dist/runtime/handoff.js.map +1 -0
- package/dist/runtime/messages.d.ts +22 -0
- package/dist/runtime/messages.d.ts.map +1 -0
- package/dist/runtime/messages.js +204 -0
- package/dist/runtime/messages.js.map +1 -0
- package/dist/runtime/provider-events.js +117 -0
- package/dist/runtime/provider-events.js.map +1 -0
- package/dist/runtime/run-compaction.js +210 -0
- package/dist/runtime/run-compaction.js.map +1 -0
- package/dist/runtime/run-finish.js +48 -0
- package/dist/runtime/run-finish.js.map +1 -0
- package/dist/runtime/run-gates.js +336 -0
- package/dist/runtime/run-gates.js.map +1 -0
- package/dist/runtime/run-init.js +81 -0
- package/dist/runtime/run-init.js.map +1 -0
- package/dist/runtime/run-input.js +46 -0
- package/dist/runtime/run-input.js.map +1 -0
- package/dist/runtime/step-catalogue.js +173 -0
- package/dist/runtime/step-catalogue.js.map +1 -0
- package/dist/runtime/tool-call-walk.js +189 -0
- package/dist/runtime/tool-call-walk.js.map +1 -0
- package/dist/runtime/tool-wiring.js +159 -0
- package/dist/runtime/tool-wiring.js.map +1 -0
- package/dist/tooling/adapters.js +1 -1
- package/dist/tooling/dataflow.js +1 -1
- package/dist/tooling/policy.js +2 -0
- package/dist/tooling/policy.js.map +1 -1
- package/dist/types.d.ts +63 -13
- package/dist/types.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/errors/index.ts +320 -0
- package/src/evaluator-optimizer/index.ts +212 -0
- package/src/factory.ts +957 -0
- package/src/fallback/index.ts +108 -0
- package/src/fanout/index.ts +523 -0
- package/src/filters/index.ts +347 -0
- package/src/index.ts +180 -0
- package/src/internal/ids.ts +46 -0
- package/src/internal/usage-accumulator.ts +90 -0
- package/src/lateral-leak/causality-monitor.ts +221 -0
- package/src/lateral-leak/index.ts +35 -0
- package/src/lateral-leak/merge-guard.ts +151 -0
- package/src/lateral-leak/protocol-guard.ts +222 -0
- package/src/preferred-model/index.ts +210 -0
- package/src/progress/index.ts +238 -0
- package/src/run-state/index.ts +607 -0
- package/src/runtime/agent-surface.ts +218 -0
- package/src/runtime/agent-to-tool.ts +323 -0
- package/src/runtime/approvals.ts +0 -0
- package/src/runtime/dispatch.ts +183 -0
- package/src/runtime/executor-wiring.ts +331 -0
- package/src/runtime/fallback-chain.ts +250 -0
- package/src/runtime/handoff.ts +428 -0
- package/src/runtime/messages.ts +309 -0
- package/src/runtime/provider-events.ts +175 -0
- package/src/runtime/run-compaction.ts +288 -0
- package/src/runtime/run-finish.ts +93 -0
- package/src/runtime/run-gates.ts +419 -0
- package/src/runtime/run-init.ts +169 -0
- package/src/runtime/run-input.ts +102 -0
- package/src/runtime/step-catalogue.ts +338 -0
- package/src/runtime/tool-call-walk.ts +301 -0
- package/src/runtime/tool-wiring.ts +218 -0
- package/src/testing/replay-provider.ts +121 -0
- package/src/tooling/adapters.ts +403 -0
- package/src/tooling/catalogue.ts +36 -0
- package/src/tooling/dataflow.ts +171 -0
- package/src/tooling/plan.ts +123 -0
- package/src/tooling/policy.ts +67 -0
- package/src/tooling/registry-build.ts +191 -0
- package/src/types.ts +696 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Warm-up tool wiring for the agent runtime: registration of the
|
|
3
|
+
* built-in `tool_search` / `read_result` tools, code-mode meta-tool
|
|
4
|
+
* wiring, result-reader composition, and the `tool_search` promotion
|
|
5
|
+
* fold. Extracted verbatim from `factory.ts` (issue #23).
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Tool } from '@graphorin/core';
|
|
11
|
+
import { createReadResultTool, createToolSearchTool } from '@graphorin/tools/built-in';
|
|
12
|
+
import {
|
|
13
|
+
type CodeExecuteBridge,
|
|
14
|
+
createCodeExecuteTool,
|
|
15
|
+
createCodeSearchTool,
|
|
16
|
+
type ProjectableTool,
|
|
17
|
+
projectToolApi,
|
|
18
|
+
} from '@graphorin/tools/code-mode';
|
|
19
|
+
import type { ToolExecutor } from '@graphorin/tools/executor';
|
|
20
|
+
import type { ToolRegistry } from '@graphorin/tools/registry';
|
|
21
|
+
import type { ResultReader } from '@graphorin/tools/result';
|
|
22
|
+
import { newId } from '../internal/ids.js';
|
|
23
|
+
|
|
24
|
+
/** The built-in deferred-discovery tool's stable name (WI-05 / P0-3). */
|
|
25
|
+
export const TOOL_SEARCH_NAME = 'tool_search';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Register the built-in `tool_search` into `registry` when - and only
|
|
29
|
+
* when - the registry holds at least one deferred tool
|
|
30
|
+
* (`defer_loading: true`). `tool_search` is itself eager (so it is
|
|
31
|
+
* always advertised while a deferred pool exists) and resolvable by the
|
|
32
|
+
* executor like any other tool, so a model can both *see* it in the
|
|
33
|
+
* per-step catalogue and *call* it.
|
|
34
|
+
*
|
|
35
|
+
* No-op when nothing defers (zero overhead - the tool never appears) or
|
|
36
|
+
* when a user tool already occupies the name (the user's tool wins; we
|
|
37
|
+
* never clobber it). Because deferral is decided at registration time
|
|
38
|
+
* (`normaliseTool`), the deferred set is fixed for the life of the
|
|
39
|
+
* registry - this runs once per registry, not per step.
|
|
40
|
+
*/
|
|
41
|
+
export function registerToolSearch(
|
|
42
|
+
registry: ToolRegistry,
|
|
43
|
+
availability?: 'next-step' | 'next-run',
|
|
44
|
+
): void {
|
|
45
|
+
if (registry.listDeferred().length === 0) return;
|
|
46
|
+
if (registry.get(TOOL_SEARCH_NAME) !== undefined) return;
|
|
47
|
+
registry.register(
|
|
48
|
+
createToolSearchTool({ registry, ...(availability !== undefined ? { availability } : {}) }),
|
|
49
|
+
{
|
|
50
|
+
kind: 'built-in',
|
|
51
|
+
subsystem: 'tool-discovery',
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The built-in result-handle reader tool's stable name (WI-10 / P1-4). */
|
|
57
|
+
export const READ_RESULT_NAME = 'read_result';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Register the built-in `read_result` into `registry` when at least one
|
|
61
|
+
* registered tool opts into the `'spill-to-file'` truncation strategy
|
|
62
|
+
* (the sole producer of spill handles today) - or when `force` is set,
|
|
63
|
+
* which the agent passes when an operator wires external result readers
|
|
64
|
+
* (e.g. an MCP `resource_link` reader; WI-13). The tool is eager, so it
|
|
65
|
+
* is advertised alongside the producing tool and the model can fetch a
|
|
66
|
+
* handle back on demand instead of inlining the full blob. No-op when
|
|
67
|
+
* nothing produces handles (zero overhead) or when a user tool already
|
|
68
|
+
* occupies the name (the user's tool wins).
|
|
69
|
+
*/
|
|
70
|
+
export function registerReadResult(
|
|
71
|
+
registry: ToolRegistry,
|
|
72
|
+
reader: ResultReader,
|
|
73
|
+
opts?: { readonly force?: boolean },
|
|
74
|
+
): void {
|
|
75
|
+
if (
|
|
76
|
+
opts?.force !== true &&
|
|
77
|
+
!registry.list().some((entry) => entry.truncationStrategy === 'spill-to-file')
|
|
78
|
+
) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (registry.get(READ_RESULT_NAME) !== undefined) return;
|
|
82
|
+
registry.register(createReadResultTool({ reader }), {
|
|
83
|
+
kind: 'built-in',
|
|
84
|
+
subsystem: 'result-handle',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Compose result readers into one that tries each in order, returning
|
|
90
|
+
* the first that resolves the handle (WI-13). The spill-file reader is
|
|
91
|
+
* placed first so `graphorin-spill:` handles resolve locally; operator
|
|
92
|
+
* readers (e.g. an MCP resource reader) resolve the rest. Each reader
|
|
93
|
+
* rejects handles it does not own, so resolution falls through cleanly.
|
|
94
|
+
*/
|
|
95
|
+
export function composeResultReaders(readers: ReadonlyArray<ResultReader>): ResultReader {
|
|
96
|
+
return {
|
|
97
|
+
async read(uri, range) {
|
|
98
|
+
let lastError: unknown;
|
|
99
|
+
for (const r of readers) {
|
|
100
|
+
try {
|
|
101
|
+
return await r.read(uri, range);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
lastError = err;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throw lastError instanceof Error
|
|
107
|
+
? lastError
|
|
108
|
+
: new Error(`No result reader resolved handle ${JSON.stringify(uri)}.`);
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The code-mode meta-tools' stable names (WI-11 / P1-2). */
|
|
114
|
+
export const CODE_EXECUTE_NAME = 'code_execute';
|
|
115
|
+
export const CODE_SEARCH_NAME = 'code_search';
|
|
116
|
+
|
|
117
|
+
/** Structural check: is this tool approval-gated (static or predicate form)? */
|
|
118
|
+
export const isApprovalGated = (t: { readonly needsApproval?: unknown }): boolean =>
|
|
119
|
+
t.needsApproval === true || typeof t.needsApproval === 'function';
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Wire code-mode (P1-2) into `registry`: register the `code_search` /
|
|
123
|
+
* `code_execute` meta-tools and return them as the tools to advertise in
|
|
124
|
+
* place of the full catalogue. The model reaches every other tool through
|
|
125
|
+
* `code_execute`, whose in-script `tools.<name>(args)` calls route back
|
|
126
|
+
* through `quietExecutor.executeOne(...)` under the calling step's
|
|
127
|
+
* `runContext` - so per-tool ACL / sanitization / truncation still apply,
|
|
128
|
+
* exactly as in direct mode. `quietExecutor` carries no `streamingSink`,
|
|
129
|
+
* so the inner calls do not interleave `tool.execute.*` events into the
|
|
130
|
+
* outer stream.
|
|
131
|
+
*
|
|
132
|
+
* Excluded from the code API (`reservedNames`): the meta-tools, the
|
|
133
|
+
* discovery / handle built-ins, handoff tools (which stay first-class
|
|
134
|
+
* provider tools), and - supplied by the caller - any approval-gated
|
|
135
|
+
* tool, since code-mode has no durable-HITL path to suspend mid-script.
|
|
136
|
+
*
|
|
137
|
+
* Returns `[]` (registering nothing) when no real tool is exposable.
|
|
138
|
+
*/
|
|
139
|
+
export function registerCodeMode(
|
|
140
|
+
registry: ToolRegistry,
|
|
141
|
+
quietExecutor: ToolExecutor,
|
|
142
|
+
reservedNames: ReadonlySet<string>,
|
|
143
|
+
getRunCapability?: () => 'read-only' | undefined,
|
|
144
|
+
): ReadonlyArray<Tool<unknown, unknown, unknown>> {
|
|
145
|
+
if (registry.get(CODE_EXECUTE_NAME) !== undefined) return []; // already wired
|
|
146
|
+
const codeTools = registry
|
|
147
|
+
.list()
|
|
148
|
+
.filter((entry) => !reservedNames.has(entry.name) && !isApprovalGated(entry));
|
|
149
|
+
if (codeTools.length === 0) return [];
|
|
150
|
+
|
|
151
|
+
// TL-8: gated tools cannot suspend for HITL mid-script, so they are
|
|
152
|
+
// excluded from the code API - but VISIBLY: they appear in the
|
|
153
|
+
// catalogue/search with a call-directly marker, and a bridged call
|
|
154
|
+
// fails with the same hint instead of an opaque unknown-tool error.
|
|
155
|
+
const approvalGatedTools = registry
|
|
156
|
+
.list()
|
|
157
|
+
.filter((entry) => !reservedNames.has(entry.name) && isApprovalGated(entry))
|
|
158
|
+
.map((entry) => entry.name);
|
|
159
|
+
const approvalGatedSet = new Set(approvalGatedTools);
|
|
160
|
+
|
|
161
|
+
const allowedTools = [...codeTools.map((entry) => entry.name), ...approvalGatedTools];
|
|
162
|
+
const allowedSet = new Set(codeTools.map((entry) => entry.name));
|
|
163
|
+
const eagerProjectable = codeTools.filter(
|
|
164
|
+
(entry) => entry.__effectiveDeferLoading !== true,
|
|
165
|
+
) as unknown as ReadonlyArray<ProjectableTool>;
|
|
166
|
+
const projection = projectToolApi(eagerProjectable);
|
|
167
|
+
|
|
168
|
+
const executeTool: CodeExecuteBridge = async (call, ctx) => {
|
|
169
|
+
if (approvalGatedSet.has(call.name)) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`${call.name} requires human approval and cannot run inside code_execute - call it directly as a standalone tool call so the run can suspend for the approval.`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const runCapability = getRunCapability?.();
|
|
175
|
+
const completed = await quietExecutor.executeOne({
|
|
176
|
+
call: { toolCallId: newId('codecall'), toolName: call.name, args: call.args },
|
|
177
|
+
runContext: ctx.runContext,
|
|
178
|
+
stepNumber: ctx.runContext.stepNumber,
|
|
179
|
+
// D2: in-script calls inherit the active run's capability.
|
|
180
|
+
...(runCapability !== undefined ? { capability: runCapability } : {}),
|
|
181
|
+
});
|
|
182
|
+
const { outcome } = completed;
|
|
183
|
+
if ('kind' in outcome) throw new Error(`${call.name}: ${outcome.message}`);
|
|
184
|
+
return outcome.output;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const codeSearch = createCodeSearchTool({
|
|
188
|
+
projection,
|
|
189
|
+
approvalGatedTools,
|
|
190
|
+
searchDeferred: async (query, k) =>
|
|
191
|
+
(await registry.searchDeferred(query, k)).filter((match) => allowedSet.has(match.name)),
|
|
192
|
+
});
|
|
193
|
+
const codeExecute = createCodeExecuteTool({
|
|
194
|
+
projection,
|
|
195
|
+
allowedTools,
|
|
196
|
+
executeTool,
|
|
197
|
+
approvalGatedTools,
|
|
198
|
+
});
|
|
199
|
+
registry.register(codeSearch, { kind: 'built-in', subsystem: 'code-mode' });
|
|
200
|
+
registry.register(codeExecute, { kind: 'built-in', subsystem: 'code-mode' });
|
|
201
|
+
return [codeSearch, codeExecute] as ReadonlyArray<Tool<unknown, unknown, unknown>>;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Fold a completed `tool_search` result into the per-run promotion set:
|
|
206
|
+
* every matched tool name becomes advertised (and thus callable) on the
|
|
207
|
+
* next step. Tolerant of unexpected shapes (e.g. a user-shadowed
|
|
208
|
+
* `tool_search`) - only string `name`s inside a `matches` array promote.
|
|
209
|
+
*/
|
|
210
|
+
export function recordToolSearchPromotions(output: unknown, promoted: Set<string>): void {
|
|
211
|
+
if (typeof output !== 'object' || output === null) return;
|
|
212
|
+
const matches = (output as { readonly matches?: unknown }).matches;
|
|
213
|
+
if (!Array.isArray(matches)) return;
|
|
214
|
+
for (const match of matches) {
|
|
215
|
+
const name = (match as { readonly name?: unknown } | null)?.name;
|
|
216
|
+
if (typeof name === 'string') promoted.add(name);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createReplayProvider(state)` - a Provider that replays the model
|
|
3
|
+
* responses journaled on `RunState.steps[].providerResponse` (C3, opt-in
|
|
4
|
+
* via `AgentConfig.recordProviderResponses`). Re-running the same input
|
|
5
|
+
* against a replay provider reproduces the original run deterministically
|
|
6
|
+
* with zero live model calls - the mocked-completion harness that gives
|
|
7
|
+
* agent loops reproducible integration tests.
|
|
8
|
+
*
|
|
9
|
+
* The provider is strict by design: it throws when the state carries no
|
|
10
|
+
* journaled responses, and emits an error event when the replayed run
|
|
11
|
+
* asks for more steps than were recorded (a divergence, not a fixture
|
|
12
|
+
* gap - fail loudly instead of hallucinating a response).
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
Provider,
|
|
19
|
+
ProviderEvent,
|
|
20
|
+
ProviderRequest,
|
|
21
|
+
ProviderResponse,
|
|
22
|
+
RunState,
|
|
23
|
+
RunStepProviderResponse,
|
|
24
|
+
} from '@graphorin/core';
|
|
25
|
+
|
|
26
|
+
/** Options for {@link createReplayProvider}. */
|
|
27
|
+
export interface ReplayProviderOptions {
|
|
28
|
+
/** Provider name for spans/logs. Default `'replay'`. */
|
|
29
|
+
readonly name?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build a Provider that serves the journaled step responses in order.
|
|
34
|
+
*
|
|
35
|
+
* @stable
|
|
36
|
+
*/
|
|
37
|
+
export function createReplayProvider(
|
|
38
|
+
state: RunState,
|
|
39
|
+
options: ReplayProviderOptions = {},
|
|
40
|
+
): Provider {
|
|
41
|
+
const recorded: RunStepProviderResponse[] = [];
|
|
42
|
+
for (const step of state.steps) {
|
|
43
|
+
if (step.providerResponse !== undefined) recorded.push(step.providerResponse);
|
|
44
|
+
}
|
|
45
|
+
if (recorded.length === 0) {
|
|
46
|
+
throw new TypeError(
|
|
47
|
+
'createReplayProvider: RunState carries no journaled provider responses - ' +
|
|
48
|
+
'run the original agent with recordProviderResponses: true.',
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
let cursor = 0;
|
|
52
|
+
const modelId = recorded[0]?.modelId ?? 'replay';
|
|
53
|
+
|
|
54
|
+
function nextRecorded(): RunStepProviderResponse | undefined {
|
|
55
|
+
const entry = recorded[cursor];
|
|
56
|
+
cursor += 1;
|
|
57
|
+
return entry;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
name: options.name ?? 'replay',
|
|
62
|
+
modelId,
|
|
63
|
+
capabilities: {
|
|
64
|
+
streaming: true,
|
|
65
|
+
toolCalling: true,
|
|
66
|
+
parallelToolCalls: true,
|
|
67
|
+
multimodal: false,
|
|
68
|
+
structuredOutput: true,
|
|
69
|
+
reasoning: false,
|
|
70
|
+
contextWindow: 1_000_000,
|
|
71
|
+
maxOutput: 1_000_000,
|
|
72
|
+
},
|
|
73
|
+
async *stream(_req: ProviderRequest): AsyncIterable<ProviderEvent> {
|
|
74
|
+
const entry = nextRecorded();
|
|
75
|
+
if (entry === undefined) {
|
|
76
|
+
yield {
|
|
77
|
+
type: 'error',
|
|
78
|
+
error: {
|
|
79
|
+
kind: 'unknown',
|
|
80
|
+
message: `replay exhausted after ${recorded.length} recorded step(s) - the replayed run diverged from the original`,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
yield { type: 'stream-start', metadata: { providerName: 'replay', modelId: entry.modelId } };
|
|
86
|
+
if (entry.text !== undefined && entry.text.length > 0) {
|
|
87
|
+
yield { type: 'text-delta', delta: entry.text };
|
|
88
|
+
}
|
|
89
|
+
for (const call of entry.toolCalls ?? []) {
|
|
90
|
+
yield { type: 'tool-call-start', toolCallId: call.toolCallId, toolName: call.toolName };
|
|
91
|
+
yield {
|
|
92
|
+
type: 'tool-call-input-delta',
|
|
93
|
+
toolCallId: call.toolCallId,
|
|
94
|
+
argsDelta: JSON.stringify(call.args),
|
|
95
|
+
};
|
|
96
|
+
yield { type: 'tool-call-end', toolCallId: call.toolCallId, finalArgs: call.args };
|
|
97
|
+
}
|
|
98
|
+
yield {
|
|
99
|
+
type: 'finish',
|
|
100
|
+
finishReason: (entry.toolCalls?.length ?? 0) > 0 ? 'tool-calls' : 'stop',
|
|
101
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
async generate(_req: ProviderRequest): Promise<ProviderResponse> {
|
|
105
|
+
const entry = nextRecorded();
|
|
106
|
+
if (entry === undefined) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`replay exhausted after ${recorded.length} recorded step(s) - the replayed run diverged from the original`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
...(entry.text !== undefined ? { text: entry.text } : {}),
|
|
113
|
+
...(entry.toolCalls !== undefined && entry.toolCalls.length > 0
|
|
114
|
+
? { toolCalls: entry.toolCalls.map((c) => ({ ...c })) }
|
|
115
|
+
: {}),
|
|
116
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
117
|
+
finishReason: (entry.toolCalls?.length ?? 0) > 0 ? 'tool-calls' : 'stop',
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|