@zhivex-ai/core 0.20.0 → 1.0.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 +19 -1
- package/dist/agent-control-plane.d.ts +4 -1
- package/dist/agent-control-plane.d.ts.map +1 -1
- package/dist/agent-control-plane.js +61 -32
- package/dist/agent-control-plane.js.map +1 -1
- package/dist/agent-evaluation.d.ts +5 -1
- package/dist/agent-evaluation.d.ts.map +1 -1
- package/dist/agent-evaluation.js +17 -0
- package/dist/agent-evaluation.js.map +1 -1
- package/dist/agent-harness.d.ts +9 -0
- package/dist/agent-harness.d.ts.map +1 -0
- package/dist/agent-harness.js +66 -0
- package/dist/agent-harness.js.map +1 -0
- package/dist/agent-state.d.ts.map +1 -1
- package/dist/agent-state.js +112 -0
- package/dist/agent-state.js.map +1 -1
- package/dist/agent.d.ts +24 -15
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +692 -39
- package/dist/agent.js.map +1 -1
- package/dist/api-stability.d.ts.map +1 -1
- package/dist/api-stability.js +4 -0
- package/dist/api-stability.js.map +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/generate-object.d.ts.map +1 -1
- package/dist/generate-object.js +7 -2
- package/dist/generate-object.js.map +1 -1
- package/dist/generate-text.d.ts +5 -4
- package/dist/generate-text.d.ts.map +1 -1
- package/dist/generate-text.js +482 -70
- package/dist/generate-text.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +22 -3
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +164 -18
- package/dist/mcp.js.map +1 -1
- package/dist/messages.d.ts +5 -5
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js.map +1 -1
- package/dist/safety-policy.d.ts.map +1 -1
- package/dist/safety-policy.js +2 -3
- package/dist/safety-policy.js.map +1 -1
- package/dist/stream.d.ts +4 -0
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +29 -0
- package/dist/stream.js.map +1 -1
- package/dist/structured-output-prompt.d.ts +6 -0
- package/dist/structured-output-prompt.d.ts.map +1 -0
- package/dist/structured-output-prompt.js +26 -0
- package/dist/structured-output-prompt.js.map +1 -0
- package/dist/tool-execution-suspension.d.ts +8 -0
- package/dist/tool-execution-suspension.d.ts.map +1 -0
- package/dist/tool-execution-suspension.js +12 -0
- package/dist/tool-execution-suspension.js.map +1 -0
- package/dist/types.d.ts +335 -28
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +5 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +68 -2
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
package/dist/generate-text.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { BoundedReplayBroadcast } from "./bounded-broadcast.js";
|
|
2
|
-
import { ParseError, UnsupportedFeatureError, ValidationError } from "./errors.js";
|
|
3
|
+
import { GuardrailTriggeredError, ParseError, UnsupportedFeatureError, ValidationError } from "./errors.js";
|
|
3
4
|
import { emitLanguageModelTelemetryEvent } from "./middleware.js";
|
|
4
5
|
import { createTextMessage, getTextFromMessages, isCallableToolDefinition, normalizeFinishReason, providerDataPart, resultMessages, serializeJsonValue, toolCallPart, toolResultPart, validateMessageParts } from "./messages.js";
|
|
5
6
|
import { mergeAbortSignals } from "./runtime.js";
|
|
6
7
|
import { toToolSet } from "./tool-registry.js";
|
|
8
|
+
import { ToolExecutionSuspendedError } from "./tool-execution-suspension.js";
|
|
7
9
|
const withToolTimeout = async (operation, timeoutMs, abortSignal) => {
|
|
8
10
|
if (!timeoutMs) {
|
|
9
11
|
return operation(abortSignal);
|
|
@@ -121,8 +123,52 @@ const toRequest = (options, messages) => ({
|
|
|
121
123
|
maxRetries: options.maxRetries,
|
|
122
124
|
retryBackoffMs: options.retryBackoffMs
|
|
123
125
|
});
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
+
const canonicalJson = (value) => {
|
|
127
|
+
if (value === null || typeof value !== "object") {
|
|
128
|
+
return JSON.stringify(value);
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(value)) {
|
|
131
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
132
|
+
}
|
|
133
|
+
return `{${Object.keys(value)
|
|
134
|
+
.sort()
|
|
135
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
136
|
+
.join(",")}}`;
|
|
137
|
+
};
|
|
138
|
+
const localApprovalBinding = (options, item, step) => {
|
|
139
|
+
const input = serializeJsonValue(item.parsedInput);
|
|
140
|
+
const toolVersion = item.tool.approvalVersion ?? "1";
|
|
141
|
+
const payload = canonicalJson({
|
|
142
|
+
runId: options.toolContext?.runId ?? null,
|
|
143
|
+
step,
|
|
144
|
+
toolCallId: item.call.id,
|
|
145
|
+
toolName: item.call.name,
|
|
146
|
+
input,
|
|
147
|
+
toolVersion
|
|
148
|
+
});
|
|
149
|
+
const inputDigest = createHash("sha256").update(payload).digest("hex");
|
|
150
|
+
return {
|
|
151
|
+
id: `approval_${inputDigest}`,
|
|
152
|
+
input,
|
|
153
|
+
inputDigest,
|
|
154
|
+
payload,
|
|
155
|
+
toolVersion
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
const localApprovalResolutionPayload = (inputDigest, approve, reason) => JSON.stringify({
|
|
159
|
+
inputDigest,
|
|
160
|
+
approve,
|
|
161
|
+
reason: reason ?? null
|
|
162
|
+
});
|
|
163
|
+
const normalizeApprovalDecision = (rawDecision, toolName) => typeof rawDecision === "boolean"
|
|
164
|
+
? {
|
|
165
|
+
approved: rawDecision,
|
|
166
|
+
reason: rawDecision ? undefined : `Tool "${toolName}" was denied by the approval policy.`
|
|
167
|
+
}
|
|
168
|
+
: rawDecision ?? { approved: true };
|
|
169
|
+
const validateToolCalls = async (toolCalls, options, context) => {
|
|
170
|
+
const validated = [];
|
|
171
|
+
for (const call of toolCalls) {
|
|
126
172
|
const tool = context.tools[call.name];
|
|
127
173
|
if (!tool) {
|
|
128
174
|
throw new ValidationError(`Tool "${call.name}" was requested by the model but is not registered.`);
|
|
@@ -134,56 +180,165 @@ const executeTools = async (toolCalls, options, context) => {
|
|
|
134
180
|
if (!parsed.success) {
|
|
135
181
|
throw new ValidationError(`Invalid input for tool "${call.name}": ${parsed.error.message}`);
|
|
136
182
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
};
|
|
142
|
-
});
|
|
143
|
-
const parallel = options.toolExecution?.parallel ?? options.model.capabilities.parallelToolCalls;
|
|
144
|
-
const maxConcurrency = Math.max(1, options.toolExecution?.maxConcurrency ?? validatedCalls.length ?? 1);
|
|
145
|
-
const timeoutMs = options.toolExecution?.timeoutMs;
|
|
146
|
-
const stopOnError = options.toolExecution?.stopOnError ?? false;
|
|
147
|
-
const results = new Array(validatedCalls.length);
|
|
148
|
-
const evaluateApproval = async (item) => {
|
|
149
|
-
const request = {
|
|
150
|
-
toolCall: item.call,
|
|
151
|
-
tool: item.tool,
|
|
152
|
-
input: serializeJsonValue(item.parsedInput),
|
|
183
|
+
const executionContext = {
|
|
184
|
+
...options.toolContext,
|
|
185
|
+
abortSignal: context.request.abortSignal,
|
|
186
|
+
toolCall: call,
|
|
153
187
|
step: context.step,
|
|
154
188
|
model: options.model,
|
|
155
189
|
request: context.request
|
|
156
190
|
};
|
|
157
|
-
if (!
|
|
158
|
-
|
|
191
|
+
if (tool.isEnabled && !(await tool.isEnabled(parsed.data, executionContext))) {
|
|
192
|
+
throw new ValidationError(`Tool "${call.name}" is disabled for this execution context.`);
|
|
193
|
+
}
|
|
194
|
+
validated.push({
|
|
195
|
+
call,
|
|
196
|
+
tool,
|
|
197
|
+
parsedInput: parsed.data,
|
|
198
|
+
executionContext
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return validated;
|
|
202
|
+
};
|
|
203
|
+
const preflightTools = async (toolCalls, options, context) => {
|
|
204
|
+
const validatedCalls = await validateToolCalls(toolCalls, options, context);
|
|
205
|
+
const decisions = new Map();
|
|
206
|
+
const approvalRequests = [];
|
|
207
|
+
for (const item of validatedCalls) {
|
|
208
|
+
await runToolGuardrails(item, "input");
|
|
209
|
+
const binding = localApprovalBinding(options, item, context.step);
|
|
210
|
+
const resolution = options.toolApprovalResolutions?.find((candidate) => candidate.kind === "local-tool" &&
|
|
211
|
+
candidate.requestId === binding.id &&
|
|
212
|
+
candidate.toolCallId === item.call.id);
|
|
213
|
+
let decision;
|
|
214
|
+
if (resolution) {
|
|
215
|
+
if (resolution.inputDigest !== binding.inputDigest ||
|
|
216
|
+
resolution.toolVersion !== binding.toolVersion ||
|
|
217
|
+
resolution.step !== context.step) {
|
|
218
|
+
throw new ValidationError(`Approval request "${resolution.requestId}" no longer matches tool "${item.call.name}".`);
|
|
219
|
+
}
|
|
220
|
+
if (options.toolApprovalSigner) {
|
|
221
|
+
if (!resolution.signature) {
|
|
222
|
+
throw new ValidationError(`Approval request "${resolution.requestId}" is missing its required signature.`);
|
|
223
|
+
}
|
|
224
|
+
const resolutionPayload = localApprovalResolutionPayload(resolution.inputDigest, resolution.approve, resolution.reason);
|
|
225
|
+
const validSignature = options.toolApprovalSigner.verify
|
|
226
|
+
? await options.toolApprovalSigner.verify(resolutionPayload, resolution.signature)
|
|
227
|
+
: (await options.toolApprovalSigner.sign(resolutionPayload)) === resolution.signature;
|
|
228
|
+
if (!validSignature) {
|
|
229
|
+
throw new ValidationError(`Approval request "${resolution.requestId}" has an invalid signature.`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
decision = {
|
|
233
|
+
approved: resolution.approve,
|
|
234
|
+
reason: resolution.reason
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
else if (item.tool.requiresApproval && item.tool.approvalMode === "interrupt") {
|
|
238
|
+
decision = {
|
|
239
|
+
approved: false,
|
|
240
|
+
approvalRequired: true,
|
|
241
|
+
reason: `Tool "${item.call.name}" requires human approval.`
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
else if (!options.toolApprovalPolicy) {
|
|
245
|
+
decision = item.tool.requiresApproval
|
|
159
246
|
? {
|
|
160
247
|
approved: false,
|
|
161
248
|
reason: `Tool "${item.call.name}" requires approval, but no toolApprovalPolicy is configured.`
|
|
162
249
|
}
|
|
163
250
|
: { approved: true };
|
|
164
|
-
await options.onToolApprovalDecision?.({
|
|
165
|
-
request,
|
|
166
|
-
decision
|
|
167
|
-
});
|
|
168
|
-
return decision;
|
|
169
251
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
252
|
+
else {
|
|
253
|
+
const approvalRequest = {
|
|
254
|
+
toolCall: item.call,
|
|
255
|
+
tool: item.tool,
|
|
256
|
+
input: binding.input,
|
|
257
|
+
step: context.step,
|
|
258
|
+
model: options.model,
|
|
259
|
+
request: context.request,
|
|
260
|
+
executionContext: item.executionContext
|
|
261
|
+
};
|
|
262
|
+
decision = normalizeApprovalDecision(await options.toolApprovalPolicy(approvalRequest), item.call.name);
|
|
263
|
+
}
|
|
264
|
+
const approvalRequest = {
|
|
265
|
+
toolCall: item.call,
|
|
266
|
+
tool: item.tool,
|
|
267
|
+
input: binding.input,
|
|
268
|
+
step: context.step,
|
|
269
|
+
model: options.model,
|
|
270
|
+
request: context.request,
|
|
271
|
+
executionContext: item.executionContext
|
|
272
|
+
};
|
|
273
|
+
if (decision.approved && decision.approvalRequired) {
|
|
274
|
+
throw new ValidationError(`Tool approval decision for "${item.call.name}" cannot be both approved and approvalRequired.`);
|
|
275
|
+
}
|
|
178
276
|
await options.onToolApprovalDecision?.({
|
|
179
|
-
request,
|
|
180
|
-
decision
|
|
277
|
+
request: approvalRequest,
|
|
278
|
+
decision
|
|
181
279
|
});
|
|
182
|
-
|
|
280
|
+
if (decision.approvalRequired) {
|
|
281
|
+
const signature = options.toolApprovalSigner
|
|
282
|
+
? await options.toolApprovalSigner.sign(binding.inputDigest)
|
|
283
|
+
: undefined;
|
|
284
|
+
approvalRequests.push({
|
|
285
|
+
kind: "local-tool",
|
|
286
|
+
provider: "zhivex",
|
|
287
|
+
id: binding.id,
|
|
288
|
+
name: item.call.name,
|
|
289
|
+
arguments: canonicalJson(binding.input),
|
|
290
|
+
toolCallId: item.call.id,
|
|
291
|
+
step: context.step,
|
|
292
|
+
inputDigest: binding.inputDigest,
|
|
293
|
+
toolVersion: binding.toolVersion,
|
|
294
|
+
signature,
|
|
295
|
+
rawData: {
|
|
296
|
+
type: "tool_approval_request",
|
|
297
|
+
id: binding.id,
|
|
298
|
+
name: item.call.name,
|
|
299
|
+
arguments: canonicalJson(binding.input),
|
|
300
|
+
tool_call_id: item.call.id,
|
|
301
|
+
step: context.step,
|
|
302
|
+
input_digest: binding.inputDigest,
|
|
303
|
+
tool_version: binding.toolVersion,
|
|
304
|
+
...(signature ? { signature } : {})
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
decisions.set(item.call.id, decision);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
validatedCalls,
|
|
314
|
+
decisions,
|
|
315
|
+
approvalRequests
|
|
183
316
|
};
|
|
317
|
+
};
|
|
318
|
+
const runToolGuardrails = async (item, stage, output) => {
|
|
319
|
+
const guardrails = stage === "input" ? item.tool.inputGuardrails : item.tool.outputGuardrails;
|
|
320
|
+
for (const guardrail of guardrails ?? []) {
|
|
321
|
+
const trigger = await guardrail({
|
|
322
|
+
tool: item.tool,
|
|
323
|
+
input: item.parsedInput,
|
|
324
|
+
context: item.executionContext,
|
|
325
|
+
...(stage === "output" ? { output } : {})
|
|
326
|
+
});
|
|
327
|
+
if (trigger?.triggered) {
|
|
328
|
+
throw new GuardrailTriggeredError(stage === "input" ? "tool-input" : "tool-output", trigger.reason ?? `Tool "${item.call.name}" ${stage} guardrail triggered.`, { metadata: trigger.metadata });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
const executeTools = async (preflight, options, context) => {
|
|
333
|
+
const { validatedCalls, decisions } = preflight;
|
|
334
|
+
const parallel = options.toolExecution?.parallel ?? options.model.capabilities.parallelToolCalls;
|
|
335
|
+
const maxConcurrency = Math.max(1, options.toolExecution?.maxConcurrency ?? validatedCalls.length ?? 1);
|
|
336
|
+
const timeoutMs = options.toolExecution?.timeoutMs;
|
|
337
|
+
const stopOnError = options.toolExecution?.stopOnError ?? false;
|
|
338
|
+
const results = new Array(validatedCalls.length);
|
|
184
339
|
const executeSingleTool = async (item, index) => {
|
|
185
|
-
const { call, tool
|
|
186
|
-
const approval =
|
|
340
|
+
const { call, tool } = item;
|
|
341
|
+
const approval = decisions.get(call.id) ?? { approved: true };
|
|
187
342
|
if (!approval.approved) {
|
|
188
343
|
results[index] = {
|
|
189
344
|
toolCallId: call.id,
|
|
@@ -206,13 +361,23 @@ const executeTools = async (toolCalls, options, context) => {
|
|
|
206
361
|
startedAt
|
|
207
362
|
});
|
|
208
363
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
364
|
+
// Schema and availability are deliberately rechecked immediately before
|
|
365
|
+
// the side effect. Input guardrails already ran in this preflight and run
|
|
366
|
+
// again when a persisted approval is resumed.
|
|
367
|
+
const reparsed = tool.schema.safeParse(call.input);
|
|
368
|
+
if (!reparsed.success) {
|
|
369
|
+
throw new ValidationError(`Invalid input for tool "${call.name}": ${reparsed.error.message}`);
|
|
370
|
+
}
|
|
371
|
+
item.parsedInput = reparsed.data;
|
|
372
|
+
if (tool.isEnabled && !(await tool.isEnabled(reparsed.data, item.executionContext))) {
|
|
373
|
+
throw new ValidationError(`Tool "${call.name}" is disabled for this execution context.`);
|
|
374
|
+
}
|
|
375
|
+
const rawOutput = await withToolTimeout(async (abortSignal) => tool.execute(item.parsedInput, {
|
|
376
|
+
...item.executionContext,
|
|
377
|
+
abortSignal
|
|
378
|
+
}), timeoutMs, context.request.abortSignal);
|
|
379
|
+
await runToolGuardrails(item, "output", rawOutput);
|
|
380
|
+
const output = serializeJsonValue(rawOutput);
|
|
216
381
|
const result = {
|
|
217
382
|
toolCallId: call.id,
|
|
218
383
|
toolName: call.name,
|
|
@@ -235,10 +400,49 @@ const executeTools = async (toolCalls, options, context) => {
|
|
|
235
400
|
});
|
|
236
401
|
}
|
|
237
402
|
catch (error) {
|
|
403
|
+
if (error instanceof ToolExecutionSuspendedError) {
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
407
|
+
let recoveredOutput;
|
|
408
|
+
if (tool.onError && !(normalizedError instanceof GuardrailTriggeredError)) {
|
|
409
|
+
recoveredOutput = await tool.onError(normalizedError, {
|
|
410
|
+
tool,
|
|
411
|
+
input: item.parsedInput,
|
|
412
|
+
context: item.executionContext
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
if (recoveredOutput !== undefined) {
|
|
416
|
+
await runToolGuardrails(item, "output", recoveredOutput);
|
|
417
|
+
const recoveredResult = {
|
|
418
|
+
toolCallId: call.id,
|
|
419
|
+
toolName: call.name,
|
|
420
|
+
output: serializeJsonValue(recoveredOutput),
|
|
421
|
+
isError: false,
|
|
422
|
+
providerMetadata: call.providerMetadata
|
|
423
|
+
};
|
|
424
|
+
results[index] = recoveredResult;
|
|
425
|
+
const finishedAt = Date.now();
|
|
426
|
+
await emitLanguageModelTelemetryEvent(options.model, {
|
|
427
|
+
type: "tool-execution-finish",
|
|
428
|
+
model: options.model,
|
|
429
|
+
input: context.request,
|
|
430
|
+
step: context.step,
|
|
431
|
+
toolCall: call,
|
|
432
|
+
toolResult: recoveredResult,
|
|
433
|
+
startedAt,
|
|
434
|
+
finishedAt,
|
|
435
|
+
latencyMs: finishedAt - startedAt
|
|
436
|
+
});
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (normalizedError instanceof GuardrailTriggeredError) {
|
|
440
|
+
throw normalizedError;
|
|
441
|
+
}
|
|
238
442
|
const result = {
|
|
239
443
|
toolCallId: call.id,
|
|
240
444
|
toolName: call.name,
|
|
241
|
-
error: { message:
|
|
445
|
+
error: { message: normalizedError.message },
|
|
242
446
|
isError: true,
|
|
243
447
|
providerMetadata: call.providerMetadata
|
|
244
448
|
};
|
|
@@ -250,7 +454,7 @@ const executeTools = async (toolCalls, options, context) => {
|
|
|
250
454
|
input: context.request,
|
|
251
455
|
step: context.step,
|
|
252
456
|
toolCall: call,
|
|
253
|
-
error:
|
|
457
|
+
error: normalizedError,
|
|
254
458
|
startedAt,
|
|
255
459
|
finishedAt,
|
|
256
460
|
latencyMs: finishedAt - startedAt
|
|
@@ -259,7 +463,15 @@ const executeTools = async (toolCalls, options, context) => {
|
|
|
259
463
|
};
|
|
260
464
|
if (!parallel || validatedCalls.length <= 1) {
|
|
261
465
|
for (const [index, item] of validatedCalls.entries()) {
|
|
262
|
-
|
|
466
|
+
try {
|
|
467
|
+
await executeSingleTool(item, index);
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
if (error instanceof ToolExecutionSuspendedError) {
|
|
471
|
+
throw new ToolExecutionSuspendedError(error.approvals, results.filter((result) => Boolean(result)));
|
|
472
|
+
}
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
263
475
|
if (stopOnError && results[index]?.isError) {
|
|
264
476
|
throw new Error(`Tool "${item.call.name}" failed: ${results[index]?.error?.message ?? "Unknown tool error."}`);
|
|
265
477
|
}
|
|
@@ -328,17 +540,61 @@ export const generateText = async (options) => {
|
|
|
328
540
|
}
|
|
329
541
|
const toolResults = [];
|
|
330
542
|
const generatedMessages = [];
|
|
543
|
+
const approvalRequests = [];
|
|
331
544
|
let finalResult;
|
|
332
545
|
const pendingToolCalls = extractUnresolvedToolCalls(allMessages);
|
|
333
546
|
if (pendingToolCalls.length) {
|
|
334
547
|
const request = toRequest(options, allMessages);
|
|
335
548
|
const step = options.stepOffset ?? 0;
|
|
336
|
-
await
|
|
337
|
-
const recoveredToolResults = await executeTools(pendingToolCalls, options, {
|
|
549
|
+
const preflight = await preflightTools(pendingToolCalls, options, {
|
|
338
550
|
request,
|
|
339
551
|
step,
|
|
340
552
|
tools: resolvedTools
|
|
341
553
|
});
|
|
554
|
+
if (preflight.approvalRequests.length) {
|
|
555
|
+
approvalRequests.push(...preflight.approvalRequests);
|
|
556
|
+
return {
|
|
557
|
+
text: "",
|
|
558
|
+
finishReason: "tool-calls",
|
|
559
|
+
steps,
|
|
560
|
+
messages: allMessages,
|
|
561
|
+
toolResults,
|
|
562
|
+
approvalRequests
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
await options.onBeforeToolExecution?.({ request, step, toolCalls: pendingToolCalls });
|
|
566
|
+
let recoveredToolResults;
|
|
567
|
+
try {
|
|
568
|
+
recoveredToolResults = await executeTools(preflight, options, {
|
|
569
|
+
request,
|
|
570
|
+
step
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
if (!(error instanceof ToolExecutionSuspendedError)) {
|
|
575
|
+
throw error;
|
|
576
|
+
}
|
|
577
|
+
toolResults.push(...error.completedResults);
|
|
578
|
+
for (const result of error.completedResults) {
|
|
579
|
+
allMessages.push({ role: "tool", parts: [toolResultPart(result)] });
|
|
580
|
+
}
|
|
581
|
+
if (error.completedResults.length) {
|
|
582
|
+
await options.onToolExecutionComplete?.({
|
|
583
|
+
request,
|
|
584
|
+
step,
|
|
585
|
+
toolResults: error.completedResults
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
approvalRequests.push(...error.approvals);
|
|
589
|
+
return {
|
|
590
|
+
text: "",
|
|
591
|
+
finishReason: "tool-calls",
|
|
592
|
+
steps,
|
|
593
|
+
messages: allMessages,
|
|
594
|
+
toolResults,
|
|
595
|
+
approvalRequests
|
|
596
|
+
};
|
|
597
|
+
}
|
|
342
598
|
toolResults.push(...recoveredToolResults);
|
|
343
599
|
for (const result of recoveredToolResults) {
|
|
344
600
|
allMessages.push({ role: "tool", parts: [toolResultPart(result)] });
|
|
@@ -346,11 +602,18 @@ export const generateText = async (options) => {
|
|
|
346
602
|
await options.onToolExecutionComplete?.({ request, step, toolResults: recoveredToolResults });
|
|
347
603
|
}
|
|
348
604
|
for (let step = 0; step < maxSteps; step += 1) {
|
|
349
|
-
const request = toRequest(options, allMessages);
|
|
350
605
|
const absoluteStep = (options.stepOffset ?? 0) + step + 1;
|
|
606
|
+
const preparedMessages = await options.prepareModelMessages?.({
|
|
607
|
+
messages: structuredClone(allMessages),
|
|
608
|
+
step: absoluteStep
|
|
609
|
+
});
|
|
610
|
+
if (preparedMessages) {
|
|
611
|
+
allMessages.splice(0, allMessages.length, ...structuredClone(preparedMessages));
|
|
612
|
+
}
|
|
613
|
+
const request = toRequest(options, allMessages);
|
|
351
614
|
await options.onBeforeModelStep?.({ request, step: absoluteStep });
|
|
352
615
|
const startedAt = Date.now();
|
|
353
|
-
|
|
616
|
+
let response = await options.model.generate(request);
|
|
354
617
|
stepTimings.set(request, { startedAt, finishedAt: Date.now() });
|
|
355
618
|
steps.push({ request, response });
|
|
356
619
|
finalResult = response;
|
|
@@ -360,16 +623,55 @@ export const generateText = async (options) => {
|
|
|
360
623
|
generatedMessages.push(...responseMessages);
|
|
361
624
|
}
|
|
362
625
|
const toolCalls = extractToolCalls(responseMessages);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
626
|
+
const preflight = toolCalls.length
|
|
627
|
+
? await preflightTools(toolCalls, options, {
|
|
628
|
+
request,
|
|
629
|
+
step: absoluteStep,
|
|
630
|
+
tools: resolvedTools
|
|
631
|
+
})
|
|
632
|
+
: undefined;
|
|
633
|
+
if (preflight?.approvalRequests.length) {
|
|
634
|
+
approvalRequests.push(...preflight.approvalRequests);
|
|
366
635
|
}
|
|
367
|
-
await options.
|
|
368
|
-
const currentToolResults = await executeTools(toolCalls, options, {
|
|
636
|
+
await options.onModelStep?.({
|
|
369
637
|
request,
|
|
638
|
+
response,
|
|
370
639
|
step: absoluteStep,
|
|
371
|
-
|
|
640
|
+
toolCalls,
|
|
641
|
+
approvalRequests: preflight?.approvalRequests ?? []
|
|
372
642
|
});
|
|
643
|
+
if (!toolCalls.length || preflight?.approvalRequests.length) {
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
await options.onBeforeToolExecution?.({ request, step: absoluteStep, toolCalls });
|
|
647
|
+
let currentToolResults;
|
|
648
|
+
try {
|
|
649
|
+
currentToolResults = await executeTools(preflight, options, {
|
|
650
|
+
request,
|
|
651
|
+
step: absoluteStep
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
catch (error) {
|
|
655
|
+
if (!(error instanceof ToolExecutionSuspendedError)) {
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
toolResults.push(...error.completedResults);
|
|
659
|
+
for (const result of error.completedResults) {
|
|
660
|
+
allMessages.push({
|
|
661
|
+
role: "tool",
|
|
662
|
+
parts: [toolResultPart(result)]
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
if (error.completedResults.length) {
|
|
666
|
+
await options.onToolExecutionComplete?.({
|
|
667
|
+
request,
|
|
668
|
+
step: absoluteStep,
|
|
669
|
+
toolResults: error.completedResults
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
approvalRequests.push(...error.approvals);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
373
675
|
toolResults.push(...currentToolResults);
|
|
374
676
|
for (const result of currentToolResults) {
|
|
375
677
|
allMessages.push({
|
|
@@ -389,7 +691,8 @@ export const generateText = async (options) => {
|
|
|
389
691
|
usage: aggregateTokenUsage(steps.map((step) => step.response.usage)),
|
|
390
692
|
steps,
|
|
391
693
|
messages: allMessages,
|
|
392
|
-
toolResults
|
|
694
|
+
toolResults,
|
|
695
|
+
approvalRequests
|
|
393
696
|
};
|
|
394
697
|
};
|
|
395
698
|
export const streamText = (options) => {
|
|
@@ -428,17 +731,72 @@ export const streamText = (options) => {
|
|
|
428
731
|
const generatedMessages = [];
|
|
429
732
|
const steps = [];
|
|
430
733
|
const toolResults = [];
|
|
734
|
+
const approvalRequests = [];
|
|
431
735
|
let finalResult;
|
|
432
736
|
const pendingToolCalls = extractUnresolvedToolCalls(allMessages);
|
|
433
737
|
if (pendingToolCalls.length) {
|
|
434
738
|
const request = toRequest(options, allMessages);
|
|
435
739
|
const step = options.stepOffset ?? 0;
|
|
436
|
-
await
|
|
437
|
-
const recoveredToolResults = await executeTools(pendingToolCalls, options, {
|
|
740
|
+
const preflight = await preflightTools(pendingToolCalls, options, {
|
|
438
741
|
request,
|
|
439
742
|
step,
|
|
440
743
|
tools: resolvedTools
|
|
441
744
|
});
|
|
745
|
+
if (preflight.approvalRequests.length) {
|
|
746
|
+
approvalRequests.push(...preflight.approvalRequests);
|
|
747
|
+
for (const approval of preflight.approvalRequests) {
|
|
748
|
+
await publish({ type: "tool-approval-request", approval });
|
|
749
|
+
}
|
|
750
|
+
await publish({ type: "finish", finishReason: "tool-calls" }, true);
|
|
751
|
+
broadcast.close();
|
|
752
|
+
return {
|
|
753
|
+
text: "",
|
|
754
|
+
finishReason: "tool-calls",
|
|
755
|
+
steps,
|
|
756
|
+
messages: allMessages,
|
|
757
|
+
toolResults,
|
|
758
|
+
approvalRequests
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
await options.onBeforeToolExecution?.({ request, step, toolCalls: pendingToolCalls });
|
|
762
|
+
let recoveredToolResults;
|
|
763
|
+
try {
|
|
764
|
+
recoveredToolResults = await executeTools(preflight, options, {
|
|
765
|
+
request,
|
|
766
|
+
step
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
catch (error) {
|
|
770
|
+
if (!(error instanceof ToolExecutionSuspendedError)) {
|
|
771
|
+
throw error;
|
|
772
|
+
}
|
|
773
|
+
toolResults.push(...error.completedResults);
|
|
774
|
+
for (const result of error.completedResults) {
|
|
775
|
+
await publish({ type: "tool-result", toolResult: result });
|
|
776
|
+
allMessages.push({ role: "tool", parts: [toolResultPart(result)] });
|
|
777
|
+
}
|
|
778
|
+
if (error.completedResults.length) {
|
|
779
|
+
await options.onToolExecutionComplete?.({
|
|
780
|
+
request,
|
|
781
|
+
step,
|
|
782
|
+
toolResults: error.completedResults
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
approvalRequests.push(...error.approvals);
|
|
786
|
+
for (const approval of error.approvals) {
|
|
787
|
+
await publish({ type: "tool-approval-request", approval });
|
|
788
|
+
}
|
|
789
|
+
await publish({ type: "finish", finishReason: "tool-calls" }, true);
|
|
790
|
+
broadcast.close();
|
|
791
|
+
return {
|
|
792
|
+
text: "",
|
|
793
|
+
finishReason: "tool-calls",
|
|
794
|
+
steps,
|
|
795
|
+
messages: allMessages,
|
|
796
|
+
toolResults,
|
|
797
|
+
approvalRequests
|
|
798
|
+
};
|
|
799
|
+
}
|
|
442
800
|
toolResults.push(...recoveredToolResults);
|
|
443
801
|
for (const result of recoveredToolResults) {
|
|
444
802
|
await publish({ type: "tool-result", toolResult: result });
|
|
@@ -447,8 +805,15 @@ export const streamText = (options) => {
|
|
|
447
805
|
await options.onToolExecutionComplete?.({ request, step, toolResults: recoveredToolResults });
|
|
448
806
|
}
|
|
449
807
|
for (let step = 0; step < maxSteps; step += 1) {
|
|
450
|
-
const request = toRequest(options, allMessages);
|
|
451
808
|
const absoluteStep = (options.stepOffset ?? 0) + step + 1;
|
|
809
|
+
const preparedMessages = await options.prepareModelMessages?.({
|
|
810
|
+
messages: structuredClone(allMessages),
|
|
811
|
+
step: absoluteStep
|
|
812
|
+
});
|
|
813
|
+
if (preparedMessages) {
|
|
814
|
+
allMessages.splice(0, allMessages.length, ...structuredClone(preparedMessages));
|
|
815
|
+
}
|
|
816
|
+
const request = toRequest(options, allMessages);
|
|
452
817
|
await options.onBeforeModelStep?.({ request, step: absoluteStep });
|
|
453
818
|
const startedAt = Date.now();
|
|
454
819
|
const stream = await streamModel(request);
|
|
@@ -518,16 +883,62 @@ export const streamText = (options) => {
|
|
|
518
883
|
allMessages.push(...stepMessages);
|
|
519
884
|
generatedMessages.push(...stepMessages);
|
|
520
885
|
const toolCalls = extractToolCalls(stepMessages);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
886
|
+
const preflight = toolCalls.length
|
|
887
|
+
? await preflightTools(toolCalls, options, {
|
|
888
|
+
request,
|
|
889
|
+
step: absoluteStep,
|
|
890
|
+
tools: resolvedTools
|
|
891
|
+
})
|
|
892
|
+
: undefined;
|
|
893
|
+
if (preflight?.approvalRequests.length) {
|
|
894
|
+
approvalRequests.push(...preflight.approvalRequests);
|
|
895
|
+
for (const approval of preflight.approvalRequests) {
|
|
896
|
+
await publish({ type: "tool-approval-request", approval });
|
|
897
|
+
}
|
|
524
898
|
}
|
|
525
|
-
await options.
|
|
526
|
-
const currentToolResults = await executeTools(toolCalls, options, {
|
|
899
|
+
await options.onModelStep?.({
|
|
527
900
|
request,
|
|
901
|
+
response: finalResult,
|
|
528
902
|
step: absoluteStep,
|
|
529
|
-
|
|
903
|
+
toolCalls,
|
|
904
|
+
approvalRequests: preflight?.approvalRequests ?? []
|
|
530
905
|
});
|
|
906
|
+
if (!toolCalls.length || preflight?.approvalRequests.length) {
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
await options.onBeforeToolExecution?.({ request, step: absoluteStep, toolCalls });
|
|
910
|
+
let currentToolResults;
|
|
911
|
+
try {
|
|
912
|
+
currentToolResults = await executeTools(preflight, options, {
|
|
913
|
+
request,
|
|
914
|
+
step: absoluteStep
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
catch (error) {
|
|
918
|
+
if (!(error instanceof ToolExecutionSuspendedError)) {
|
|
919
|
+
throw error;
|
|
920
|
+
}
|
|
921
|
+
toolResults.push(...error.completedResults);
|
|
922
|
+
for (const result of error.completedResults) {
|
|
923
|
+
await publish({ type: "tool-result", toolResult: result });
|
|
924
|
+
allMessages.push({
|
|
925
|
+
role: "tool",
|
|
926
|
+
parts: [toolResultPart(result)]
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
if (error.completedResults.length) {
|
|
930
|
+
await options.onToolExecutionComplete?.({
|
|
931
|
+
request,
|
|
932
|
+
step: absoluteStep,
|
|
933
|
+
toolResults: error.completedResults
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
approvalRequests.push(...error.approvals);
|
|
937
|
+
for (const approval of error.approvals) {
|
|
938
|
+
await publish({ type: "tool-approval-request", approval });
|
|
939
|
+
}
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
531
942
|
toolResults.push(...currentToolResults);
|
|
532
943
|
for (const toolResult of currentToolResults) {
|
|
533
944
|
await publish({ type: "tool-result", toolResult });
|
|
@@ -556,7 +967,8 @@ export const streamText = (options) => {
|
|
|
556
967
|
usage,
|
|
557
968
|
steps,
|
|
558
969
|
messages: allMessages,
|
|
559
|
-
toolResults
|
|
970
|
+
toolResults,
|
|
971
|
+
approvalRequests
|
|
560
972
|
};
|
|
561
973
|
};
|
|
562
974
|
finalResultPromise = runner().catch(async (error) => {
|