@aeye/core 0.5.4 → 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/README.md +790 -724
- package/aeye-core-prompts.md +179 -178
- package/aeye-core-schema.md +43 -16
- package/aeye-core-tools.md +179 -177
- package/aeye-core-types.md +1 -0
- package/dist/common.d.ts +30 -5
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +134 -129
- package/dist/common.js.map +1 -1
- package/dist/delivery.d.ts +39 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +127 -0
- package/dist/delivery.js.map +1 -0
- package/dist/errors.d.ts +60 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +84 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/prompt.d.ts +134 -15
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +220 -164
- package/dist/prompt.js.map +1 -1
- package/dist/schema/analyze.d.ts +94 -0
- package/dist/schema/analyze.d.ts.map +1 -0
- package/dist/schema/analyze.js +221 -0
- package/dist/schema/analyze.js.map +1 -0
- package/dist/schema/any-value.d.ts +32 -0
- package/dist/schema/any-value.d.ts.map +1 -0
- package/dist/schema/any-value.js +77 -0
- package/dist/schema/any-value.js.map +1 -0
- package/dist/schema/budget.d.ts +114 -0
- package/dist/schema/budget.d.ts.map +1 -0
- package/dist/schema/budget.js +223 -0
- package/dist/schema/budget.js.map +1 -0
- package/dist/schema/check-bag.d.ts +28 -0
- package/dist/schema/check-bag.d.ts.map +1 -0
- package/dist/schema/check-bag.js +129 -0
- package/dist/schema/check-bag.js.map +1 -0
- package/dist/schema/consistency.d.ts +27 -0
- package/dist/schema/consistency.d.ts.map +1 -0
- package/dist/schema/consistency.js +110 -0
- package/dist/schema/consistency.js.map +1 -0
- package/dist/schema/cycle-breaker.d.ts +14 -0
- package/dist/schema/cycle-breaker.d.ts.map +1 -0
- package/dist/schema/cycle-breaker.js +157 -0
- package/dist/schema/cycle-breaker.js.map +1 -0
- package/dist/schema/decode.d.ts +31 -0
- package/dist/schema/decode.d.ts.map +1 -0
- package/dist/schema/decode.js +207 -0
- package/dist/schema/decode.js.map +1 -0
- package/dist/schema/descriptor.d.ts +300 -0
- package/dist/schema/descriptor.d.ts.map +1 -0
- package/dist/schema/descriptor.js +15 -0
- package/dist/schema/descriptor.js.map +1 -0
- package/dist/schema/dialects/anthropic.d.ts +34 -0
- package/dist/schema/dialects/anthropic.d.ts.map +1 -0
- package/dist/schema/dialects/anthropic.js +77 -0
- package/dist/schema/dialects/anthropic.js.map +1 -0
- package/dist/schema/dialects/google.d.ts +47 -0
- package/dist/schema/dialects/google.d.ts.map +1 -0
- package/dist/schema/dialects/google.js +91 -0
- package/dist/schema/dialects/google.js.map +1 -0
- package/dist/schema/dialects/lenient.d.ts +4 -0
- package/dist/schema/dialects/lenient.d.ts.map +1 -0
- package/dist/schema/dialects/lenient.js +26 -0
- package/dist/schema/dialects/lenient.js.map +1 -0
- package/dist/schema/dialects/openai.d.ts +40 -0
- package/dist/schema/dialects/openai.d.ts.map +1 -0
- package/dist/schema/dialects/openai.js +81 -0
- package/dist/schema/dialects/openai.js.map +1 -0
- package/dist/schema/dialects/xai.d.ts +23 -0
- package/dist/schema/dialects/xai.d.ts.map +1 -0
- package/dist/schema/dialects/xai.js +53 -0
- package/dist/schema/dialects/xai.js.map +1 -0
- package/dist/schema/enum-values.d.ts +44 -0
- package/dist/schema/enum-values.d.ts.map +1 -0
- package/dist/schema/enum-values.js +56 -0
- package/dist/schema/enum-values.js.map +1 -0
- package/dist/schema/expressible.d.ts +59 -0
- package/dist/schema/expressible.d.ts.map +1 -0
- package/dist/schema/expressible.js +161 -0
- package/dist/schema/expressible.js.map +1 -0
- package/dist/schema/index.d.ts +20 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +20 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/json-schema.d.ts +64 -0
- package/dist/schema/json-schema.d.ts.map +1 -0
- package/dist/schema/json-schema.js +2 -0
- package/dist/schema/json-schema.js.map +1 -0
- package/dist/schema/registry.d.ts +73 -0
- package/dist/schema/registry.d.ts.map +1 -0
- package/dist/schema/registry.js +135 -0
- package/dist/schema/registry.js.map +1 -0
- package/dist/schema/strictify.d.ts +19 -0
- package/dist/schema/strictify.d.ts.map +1 -0
- package/dist/schema/strictify.js +237 -0
- package/dist/schema/strictify.js.map +1 -0
- package/dist/schema/to-json-schema.d.ts +29 -0
- package/dist/schema/to-json-schema.d.ts.map +1 -0
- package/dist/schema/to-json-schema.js +692 -0
- package/dist/schema/to-json-schema.js.map +1 -0
- package/dist/schema/walk-key.d.ts +24 -0
- package/dist/schema/walk-key.d.ts.map +1 -0
- package/dist/schema/walk-key.js +33 -0
- package/dist/schema/walk-key.js.map +1 -0
- package/dist/to.js +1 -1
- package/dist/to.js.map +1 -1
- package/dist/tool.d.ts +38 -4
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js +52 -8
- package/dist/tool.js.map +1 -1
- package/dist/types.d.ts +265 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/dist/schema.d.ts +0 -840
- package/dist/schema.d.ts.map +0 -1
- package/dist/schema.js +0 -2478
- package/dist/schema.js.map +0 -1
package/dist/prompt.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import Handlebars from "handlebars";
|
|
2
2
|
import { ZodString } from 'zod';
|
|
3
3
|
import { accumulateReasoning, accumulateUsage, extractJSONObject, getChunksFromResponse, getInputTokens, getModel, getOutputTokens, getTotalTokens, resolve, resolveFn, yieldAll } from "./common";
|
|
4
|
-
import { ToolInterrupt, PromptSuspend } from "./tool";
|
|
4
|
+
import { forcesTool, ToolInterrupt, PromptSuspend } from "./tool";
|
|
5
5
|
import { getDescriptorById, strictify, decodeWire } from "./schema";
|
|
6
|
+
import { OutputValidationError, PromptError, ReviewRejectedError, SchemaRejectedError, ToolExecutionError, ToolInputError } from "./errors";
|
|
7
|
+
import { SCHEMA_FALLBACKS, structuredOutput } from "./delivery";
|
|
6
8
|
/** Default cap (chars) for validation error messages surfaced back to the
|
|
7
9
|
* LLM. Read by `Prompt.truncateValidationError`; subclasses may override
|
|
8
10
|
* the method to ignore this. Anything past `max` is replaced with a
|
|
9
11
|
* `… (N more characters)` marker. */
|
|
10
12
|
const DEFAULT_VALIDATION_ERROR_MAX_LENGTH = 4096;
|
|
13
|
+
/**
|
|
14
|
+
* The user message a prompt adds when it runs without a conversation.
|
|
15
|
+
* Override it with `userTurn` on the prompt or the context.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_USER_TURN = 'Begin.';
|
|
11
18
|
/**
|
|
12
19
|
* A Prompt component that generates AI responses based on input, context, and available tools.
|
|
13
20
|
* Prompts orchestrate interactions with AI models, handle tool calls, and manage streaming responses.
|
|
@@ -262,7 +269,7 @@ export class Prompt {
|
|
|
262
269
|
tools,
|
|
263
270
|
responseFormat,
|
|
264
271
|
};
|
|
265
|
-
const fixedToolChoice =
|
|
272
|
+
const fixedToolChoice = forcesTool(request.toolChoice);
|
|
266
273
|
if (fixedToolChoice && (!tools || tools.length === 0)) {
|
|
267
274
|
throw new Error(`Prompt ${this.input.name} is configured to require tools, but no tools are available.`);
|
|
268
275
|
}
|
|
@@ -271,13 +278,26 @@ export class Prompt {
|
|
|
271
278
|
// Pre-emptively trim context messages if we have a context window limit
|
|
272
279
|
request.messages = this.forget(request, ctx);
|
|
273
280
|
}
|
|
281
|
+
// Without a conversation the request still needs a user turn: APIs such
|
|
282
|
+
// as Anthropic's and Bedrock's reject a request that has none
|
|
283
|
+
if (request.messages.length === 1) {
|
|
284
|
+
request.messages.push({ role: 'user', content: this.input.userTurn ?? ctx.userTurn ?? DEFAULT_USER_TURN });
|
|
285
|
+
}
|
|
274
286
|
let outputRetries = this.input.outputRetries ?? ctx.outputRetries ?? 2;
|
|
275
287
|
let forgetRetries = this.input.forgetRetries ?? ctx.forgetRetries ?? 1;
|
|
276
288
|
let toolIterations = this.input.toolIterations ?? 3;
|
|
277
289
|
let toolRetries = this.input.toolRetries ?? ctx.toolRetries ?? 2;
|
|
290
|
+
let reviewRetries = this.input.reviewRetries ?? 1;
|
|
291
|
+
let reviews = 0;
|
|
278
292
|
const toolsComplete = this.input.toolsComplete ?? true;
|
|
293
|
+
const schemaFallbacks = this.input.schemaFallbacks ?? ctx.schemaFallbacks ?? SCHEMA_FALLBACKS;
|
|
294
|
+
// Schema fallbacks taken this run, re-applied when a dynamic prompt
|
|
295
|
+
// re-resolves its request.
|
|
296
|
+
const fallbacksTaken = [];
|
|
279
297
|
let result = undefined;
|
|
280
298
|
let lastError = undefined;
|
|
299
|
+
// The last tool failure, the cause when the iterations run out.
|
|
300
|
+
let toolFailure = undefined;
|
|
281
301
|
let completeText = '';
|
|
282
302
|
// Runtime schema-delivery fallback guard. A model whose descriptor ALLOWS
|
|
283
303
|
// the schema (so the static `canExpress` check passes and structured output
|
|
@@ -290,7 +310,7 @@ export class Prompt {
|
|
|
290
310
|
// idempotent within a single prompt run — we switch at most once and never
|
|
291
311
|
// loop. Per-run only; nothing is persisted across requests.
|
|
292
312
|
let promotedToPromptDelivery = false;
|
|
293
|
-
let maxIterations = outputRetries + forgetRetries + toolIterations + toolRetries + 1;
|
|
313
|
+
let maxIterations = outputRetries + forgetRetries + toolIterations + toolRetries + reviewRetries + 1;
|
|
294
314
|
let requestUsageSent = false;
|
|
295
315
|
let usage = undefined;
|
|
296
316
|
let iterations = 0;
|
|
@@ -314,6 +334,30 @@ export class Prompt {
|
|
|
314
334
|
request.messages.push(message);
|
|
315
335
|
return emit({ type: 'message', message, request });
|
|
316
336
|
};
|
|
337
|
+
// The events a tool execution is ready to emit, each emitted once.
|
|
338
|
+
const toolEvents = function* (te) {
|
|
339
|
+
const { tool, args } = te;
|
|
340
|
+
const toolCallId = te.toolCall.id;
|
|
341
|
+
if (te.emitRepairAttempt()) {
|
|
342
|
+
yield emit({ type: 'toolArgRepairAttempt', tool: tool, fields: te.repairAttempt.fields, success: te.repairAttempt.success, request });
|
|
343
|
+
}
|
|
344
|
+
if (te.emitStart()) {
|
|
345
|
+
yield emitTool({ type: 'toolStart', tool: tool, toolCallId, args, request });
|
|
346
|
+
}
|
|
347
|
+
if (te.emitOutput()) {
|
|
348
|
+
yield emitTool({ type: 'toolOutput', tool: tool, toolCallId, args, result: te.result, toModel: te.toModel, request });
|
|
349
|
+
}
|
|
350
|
+
if (te.emitInterrupt()) {
|
|
351
|
+
yield emitTool({ type: 'toolInterrupt', tool: tool, toolCallId, args, request });
|
|
352
|
+
}
|
|
353
|
+
if (te.emitSuspend()) {
|
|
354
|
+
yield emitTool({ type: 'toolSuspend', tool: tool, toolCallId, args, request });
|
|
355
|
+
}
|
|
356
|
+
if (te.emitError()) {
|
|
357
|
+
const failure = te.error;
|
|
358
|
+
yield emitTool({ type: 'toolError', tool: tool, toolCallId, args, error: failure.message, failure, rawArgs: failure instanceof ToolInputError ? failure.rawArgs : undefined, request });
|
|
359
|
+
}
|
|
360
|
+
};
|
|
317
361
|
// Main execution loop!
|
|
318
362
|
while (iterations < maxIterations) {
|
|
319
363
|
// Honor the caller's abort signal at iteration boundaries — if a
|
|
@@ -377,6 +421,18 @@ export class Prompt {
|
|
|
377
421
|
reasoning = accumulateReasoning(reasoning, chunk.reasoning);
|
|
378
422
|
yield emit({ type: 'reasonPartial', reasoning, request });
|
|
379
423
|
}
|
|
424
|
+
if (chunk.citations) {
|
|
425
|
+
yield emit({ type: 'citations', citations: chunk.citations, request });
|
|
426
|
+
}
|
|
427
|
+
if (chunk.images) {
|
|
428
|
+
yield emit({ type: 'images', images: chunk.images, request });
|
|
429
|
+
}
|
|
430
|
+
if (chunk.audio) {
|
|
431
|
+
yield emit({ type: 'audioPartial', audio: chunk.audio, request });
|
|
432
|
+
}
|
|
433
|
+
if (chunk.moderation) {
|
|
434
|
+
yield emit({ type: 'moderation', moderation: chunk.moderation, request });
|
|
435
|
+
}
|
|
380
436
|
// Handle tool calls
|
|
381
437
|
if (chunk.toolCallNamed) {
|
|
382
438
|
const toolName = chunk.toolCallNamed.name;
|
|
@@ -405,7 +461,7 @@ export class Prompt {
|
|
|
405
461
|
// value short-circuits, false/undefined proceeds, a throw
|
|
406
462
|
// propagates through run()'s existing catch (interrupt/suspend/error).
|
|
407
463
|
onToolIntercept && toolInfo
|
|
408
|
-
? (args) => onToolIntercept(toolInfo.tool, args, ctx)
|
|
464
|
+
? (args, call) => onToolIntercept(toolInfo.tool, args, ctx, call)
|
|
409
465
|
: undefined);
|
|
410
466
|
toolExecutors.push(toolExecutor);
|
|
411
467
|
toolExecutorMap.set(chunk.toolCallNamed.id, toolExecutor);
|
|
@@ -446,21 +502,7 @@ export class Prompt {
|
|
|
446
502
|
// In immediate mode we might be getting more chunks while executing, emit events as soon as possible.
|
|
447
503
|
if (toolMode === 'immediate') {
|
|
448
504
|
for (const toolCall of toolExecutors) {
|
|
449
|
-
|
|
450
|
-
yield emitTool({ type: 'toolStart', tool: toolCall.tool, args: toolCall.args, request });
|
|
451
|
-
}
|
|
452
|
-
if (toolCall.emitOutput()) {
|
|
453
|
-
yield emitTool({ type: 'toolOutput', tool: toolCall.tool, args: toolCall.args, result: toolCall.result, toModel: toolCall.toModel, request });
|
|
454
|
-
}
|
|
455
|
-
if (toolCall.emitInterrupt()) {
|
|
456
|
-
yield emitTool({ type: 'toolInterrupt', tool: toolCall.tool, args: toolCall.args, request });
|
|
457
|
-
}
|
|
458
|
-
if (toolCall.emitSuspend()) {
|
|
459
|
-
yield emitTool({ type: 'toolSuspend', tool: toolCall.tool, args: toolCall.args, request });
|
|
460
|
-
}
|
|
461
|
-
if (toolCall.emitError()) {
|
|
462
|
-
yield emitTool({ type: 'toolError', tool: toolCall.tool, args: toolCall.args, error: toolCall.error, rawArgs: toolCall.rawArgs, request });
|
|
463
|
-
}
|
|
505
|
+
yield* toolEvents(toolCall);
|
|
464
506
|
}
|
|
465
507
|
}
|
|
466
508
|
}
|
|
@@ -472,13 +514,13 @@ export class Prompt {
|
|
|
472
514
|
// If the model refused to answer and stop
|
|
473
515
|
if (finishReason === 'refusal' || refusal) {
|
|
474
516
|
yield emit({ type: 'refusal', content: refusal || 'unspecified', request });
|
|
475
|
-
lastError = refusal || 'Model refused to answer.';
|
|
517
|
+
lastError = new PromptError(refusal || 'Model refused to answer.', 'model');
|
|
476
518
|
break;
|
|
477
519
|
}
|
|
478
520
|
// If the model was stopped due to content filtering
|
|
479
521
|
if (finishReason === 'content_filter') {
|
|
480
522
|
yield emit({ type: 'refusal', content: 'Content filtered by AI model', request });
|
|
481
|
-
lastError = 'Model response was filtered due to content policy.';
|
|
523
|
+
lastError = new PromptError('Model response was filtered due to content policy.', 'model');
|
|
482
524
|
break;
|
|
483
525
|
}
|
|
484
526
|
// If we sent too much, forget the past homie
|
|
@@ -492,7 +534,7 @@ export class Prompt {
|
|
|
492
534
|
}
|
|
493
535
|
else {
|
|
494
536
|
// Stop iteration - we can't trim without usage info
|
|
495
|
-
lastError = 'Model indicated length finish reason but no token usage was provided so context cannot be trimmed.';
|
|
537
|
+
lastError = new PromptError('Model indicated length finish reason but no token usage was provided so context cannot be trimmed.', 'model');
|
|
496
538
|
break;
|
|
497
539
|
}
|
|
498
540
|
}
|
|
@@ -520,18 +562,19 @@ export class Prompt {
|
|
|
520
562
|
// Non-blocking call, we don't want to hold up execution here. But if we can emit start or error early below this we will try.
|
|
521
563
|
toolExecutor.parse();
|
|
522
564
|
}
|
|
523
|
-
|
|
524
|
-
yield emit({ type: 'toolArgRepairAttempt', tool: toolExecutor.tool, fields: toolExecutor.repairAttempt.fields, success: toolExecutor.repairAttempt.success, request });
|
|
525
|
-
}
|
|
526
|
-
if (toolExecutor.emitStart()) {
|
|
527
|
-
yield emitTool({ type: 'toolStart', tool: toolExecutor.tool, args: toolExecutor.args, request });
|
|
528
|
-
}
|
|
529
|
-
if (toolExecutor.emitError()) {
|
|
530
|
-
yield emitTool({ type: 'toolError', tool: toolExecutor.tool, args: toolExecutor.args, error: toolExecutor.error, rawArgs: toolExecutor.rawArgs, request });
|
|
531
|
-
}
|
|
565
|
+
yield* toolEvents(toolExecutor);
|
|
532
566
|
}
|
|
533
|
-
// The execution mode for this iteration.
|
|
567
|
+
// The execution mode for this iteration. Skipping leaves every valid
|
|
568
|
+
// sibling unrun, and its reply says so.
|
|
534
569
|
const iterationMode = skip ? 'skip' : toolMode;
|
|
570
|
+
if (skip) {
|
|
571
|
+
for (const toolExecutor of toolExecutors) {
|
|
572
|
+
// Only a call that has not started: under 'immediate' a valid call may
|
|
573
|
+
// already be running, and its reply is its real outcome.
|
|
574
|
+
if (!toolExecutor.error && (toolExecutor.status === 'ready' || toolExecutor.status === 'parsed'))
|
|
575
|
+
toolExecutor.skipped = true;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
535
578
|
// All tool calls are valid, lets start this!
|
|
536
579
|
switch (iterationMode) {
|
|
537
580
|
case 'sequential':
|
|
@@ -547,25 +590,9 @@ export class Prompt {
|
|
|
547
590
|
if (ctx.signal?.aborted)
|
|
548
591
|
break;
|
|
549
592
|
await toolExecutor.parse();
|
|
550
|
-
|
|
551
|
-
yield emit({ type: 'toolArgRepairAttempt', tool: toolExecutor.tool, fields: toolExecutor.repairAttempt.fields, success: toolExecutor.repairAttempt.success, request });
|
|
552
|
-
}
|
|
553
|
-
if (toolExecutor.emitStart()) {
|
|
554
|
-
yield emitTool({ type: 'toolStart', tool: toolExecutor.tool, args: toolExecutor.args, request });
|
|
555
|
-
}
|
|
593
|
+
yield* toolEvents(toolExecutor);
|
|
556
594
|
await toolExecutor.run();
|
|
557
|
-
|
|
558
|
-
yield emitTool({ type: 'toolOutput', tool: toolExecutor.tool, args: toolExecutor.args, result: toolExecutor.result, toModel: toolExecutor.toModel, request });
|
|
559
|
-
}
|
|
560
|
-
if (toolExecutor.emitInterrupt()) {
|
|
561
|
-
yield emitTool({ type: 'toolInterrupt', tool: toolExecutor.tool, args: toolExecutor.args, request });
|
|
562
|
-
}
|
|
563
|
-
if (toolExecutor.emitSuspend()) {
|
|
564
|
-
yield emitTool({ type: 'toolSuspend', tool: toolExecutor.tool, args: toolExecutor.args, request });
|
|
565
|
-
}
|
|
566
|
-
if (toolExecutor.emitError()) {
|
|
567
|
-
yield emitTool({ type: 'toolError', tool: toolExecutor.tool, args: toolExecutor.args, error: toolExecutor.error, rawArgs: toolExecutor.rawArgs, request });
|
|
568
|
-
}
|
|
595
|
+
yield* toolEvents(toolExecutor);
|
|
569
596
|
}
|
|
570
597
|
break;
|
|
571
598
|
case 'parallel':
|
|
@@ -582,25 +609,7 @@ export class Prompt {
|
|
|
582
609
|
// listener was already removed before tool dispatch.)
|
|
583
610
|
if (ctx.signal?.aborted)
|
|
584
611
|
break;
|
|
585
|
-
|
|
586
|
-
if (toolExecutor.emitRepairAttempt()) {
|
|
587
|
-
yield emit({ type: 'toolArgRepairAttempt', tool: toolExecutor.tool, fields: toolExecutor.repairAttempt.fields, success: toolExecutor.repairAttempt.success, request });
|
|
588
|
-
}
|
|
589
|
-
if (toolExecutor.emitStart()) {
|
|
590
|
-
yield emitTool({ type: 'toolStart', tool: toolExecutor.tool, args: toolExecutor.args, request });
|
|
591
|
-
}
|
|
592
|
-
if (toolExecutor.emitOutput()) {
|
|
593
|
-
yield emitTool({ type: 'toolOutput', tool: toolExecutor.tool, args: toolExecutor.args, result: toolExecutor.result, toModel: toolExecutor.toModel, request });
|
|
594
|
-
}
|
|
595
|
-
if (toolExecutor.emitInterrupt()) {
|
|
596
|
-
yield emitTool({ type: 'toolInterrupt', tool: toolExecutor.tool, args: toolExecutor.args, request });
|
|
597
|
-
}
|
|
598
|
-
if (toolExecutor.emitSuspend()) {
|
|
599
|
-
yield emitTool({ type: 'toolSuspend', tool: toolExecutor.tool, args: toolExecutor.args, request });
|
|
600
|
-
}
|
|
601
|
-
if (toolExecutor.emitError()) {
|
|
602
|
-
yield emitTool({ type: 'toolError', tool: toolExecutor.tool, args: toolExecutor.args, error: toolExecutor.error, rawArgs: toolExecutor.rawArgs, request });
|
|
603
|
-
}
|
|
612
|
+
yield* toolEvents(await toolCallPromise);
|
|
604
613
|
}
|
|
605
614
|
break;
|
|
606
615
|
}
|
|
@@ -644,7 +653,7 @@ export class Prompt {
|
|
|
644
653
|
// exactly as before: strings verbatim, everything else JSON.
|
|
645
654
|
const modelValue = toolExecutor.status === 'success' ? toolExecutor.toModel : toolExecutor.result;
|
|
646
655
|
const content = hasError
|
|
647
|
-
? toolExecutor.error
|
|
656
|
+
? toolExecutor.error.message
|
|
648
657
|
: hasResult
|
|
649
658
|
? typeof modelValue === 'string'
|
|
650
659
|
? modelValue
|
|
@@ -654,7 +663,9 @@ export class Prompt {
|
|
|
654
663
|
role: 'tool',
|
|
655
664
|
content,
|
|
656
665
|
toolCallId: toolExecutor.toolCall.id,
|
|
666
|
+
failure: toolExecutor.error?.kind,
|
|
657
667
|
});
|
|
668
|
+
toolFailure = toolExecutor.error ?? toolFailure;
|
|
658
669
|
if (toolExecutor.status === 'invalid') {
|
|
659
670
|
toolParseErrors++;
|
|
660
671
|
}
|
|
@@ -718,9 +729,9 @@ export class Prompt {
|
|
|
718
729
|
toolExecutors.some(toolExecutor => toolExecutor.status === 'interrupted');
|
|
719
730
|
// If we are finished, parse the output
|
|
720
731
|
if (stop) {
|
|
732
|
+
let failure;
|
|
721
733
|
if (!schema || (schema instanceof ZodString)) {
|
|
722
734
|
result = content;
|
|
723
|
-
break; // All good!
|
|
724
735
|
}
|
|
725
736
|
else {
|
|
726
737
|
// Grab the JSON part from the content just in case. In the
|
|
@@ -728,9 +739,10 @@ export class Prompt {
|
|
|
728
739
|
// freeform text (possibly fenced / prose-wrapped), so extract the
|
|
729
740
|
// outermost balanced `{…}` (see `extractJSONObject`).
|
|
730
741
|
const potentialJSON = extractJSONObject(content);
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
742
|
+
const invalid = (message, reason) => new OutputValidationError(this.truncateValidationError(message, this.input.validationErrorMaxLength), reason, content);
|
|
743
|
+
// The descriptor the provider shaped the output with, when the
|
|
744
|
+
// schema went on the wire.
|
|
745
|
+
const descriptorId = structuredOutput(request.responseFormat)?.descriptor;
|
|
734
746
|
try {
|
|
735
747
|
const parsedJSON = JSON.parse(potentialJSON);
|
|
736
748
|
if (this.input.parse) {
|
|
@@ -749,11 +761,8 @@ export class Prompt {
|
|
|
749
761
|
// (no Zod vocabulary — Zod never ran). Absent ⇒ unchanged.
|
|
750
762
|
// Decode only when a descriptor was pinned; otherwise the wire
|
|
751
763
|
// shape IS the conceptual shape and `parsedJSON` passes through.
|
|
752
|
-
const
|
|
753
|
-
?
|
|
754
|
-
: undefined;
|
|
755
|
-
const decoded = customDescriptorId
|
|
756
|
-
? decodeWire(schema, parsedJSON, getDescriptorById(customDescriptorId))
|
|
764
|
+
const decoded = descriptorId
|
|
765
|
+
? decodeWire(schema, parsedJSON, getDescriptorById(descriptorId))
|
|
757
766
|
: parsedJSON;
|
|
758
767
|
let customResult;
|
|
759
768
|
try {
|
|
@@ -763,8 +772,7 @@ export class Prompt {
|
|
|
763
772
|
customResult = customError instanceof Error ? customError : new Error(String(customError));
|
|
764
773
|
}
|
|
765
774
|
if (customResult instanceof Error) {
|
|
766
|
-
|
|
767
|
-
resetReason = 'schema-parsing';
|
|
775
|
+
failure = invalid(customResult.message, 'schema-parsing');
|
|
768
776
|
}
|
|
769
777
|
else {
|
|
770
778
|
result = customResult;
|
|
@@ -772,8 +780,7 @@ export class Prompt {
|
|
|
772
780
|
await this.input.validate?.(result, ctx);
|
|
773
781
|
}
|
|
774
782
|
catch (validationError) {
|
|
775
|
-
|
|
776
|
-
resetReason = 'validation';
|
|
783
|
+
failure = invalid(`The output failed validation:\n${validationError.message}`, 'validation');
|
|
777
784
|
}
|
|
778
785
|
}
|
|
779
786
|
}
|
|
@@ -783,19 +790,15 @@ export class Prompt {
|
|
|
783
790
|
// normalize back into the natural Zod shape before validation.
|
|
784
791
|
// The descriptor was pinned on the response format when the
|
|
785
792
|
// provider built the request. Fast cache lookup on retries.
|
|
786
|
-
const
|
|
787
|
-
?
|
|
788
|
-
: undefined;
|
|
789
|
-
const validationSchema = responseDescriptorId
|
|
790
|
-
? strictify(schema, getDescriptorById(responseDescriptorId))
|
|
793
|
+
const validationSchema = descriptorId
|
|
794
|
+
? strictify(schema, getDescriptorById(descriptorId))
|
|
791
795
|
: schema;
|
|
792
796
|
const parsedSafe = await validationSchema.safeParseAsync(parsedJSON);
|
|
793
797
|
if (!parsedSafe.success) {
|
|
794
798
|
const issueSummary = parsedSafe.error.issues
|
|
795
799
|
.map(i => `- ${i.path.join('.')}: ${i.message}${['string', 'boolean', 'number'].includes(typeof i.input) ? ` (input: ${i.input})` : ''}`)
|
|
796
800
|
.join('\n');
|
|
797
|
-
|
|
798
|
-
resetReason = 'schema-parsing';
|
|
801
|
+
failure = invalid(`The output was an invalid format:\n${issueSummary}`, 'schema-parsing');
|
|
799
802
|
}
|
|
800
803
|
else {
|
|
801
804
|
result = parsedSafe.data;
|
|
@@ -803,15 +806,13 @@ export class Prompt {
|
|
|
803
806
|
await this.input.validate?.(result, ctx);
|
|
804
807
|
}
|
|
805
808
|
catch (validationError) {
|
|
806
|
-
|
|
807
|
-
resetReason = 'validation';
|
|
809
|
+
failure = invalid(`The output failed validation:\n${validationError.message}`, 'validation');
|
|
808
810
|
}
|
|
809
811
|
}
|
|
810
812
|
}
|
|
811
813
|
}
|
|
812
814
|
catch (parseError) {
|
|
813
|
-
|
|
814
|
-
resetReason = 'json-parsing';
|
|
815
|
+
failure = invalid(`The output was not valid JSON:\n${parseError.message}`, 'json-parsing');
|
|
815
816
|
}
|
|
816
817
|
// Runtime schema-delivery fallback. The structured response was EMPTY
|
|
817
818
|
// or its JSON was UNPARSEABLE (`json-parsing` reset — either the
|
|
@@ -830,39 +831,73 @@ export class Prompt {
|
|
|
830
831
|
// The switch is not remembered beyond this run. This reuses the
|
|
831
832
|
// existing outputRetries re-prompt mechanism below to re-issue.
|
|
832
833
|
if (this.input.runtimeSchemaFallback !== false &&
|
|
833
|
-
|
|
834
|
+
failure?.reason === 'json-parsing' &&
|
|
834
835
|
!promotedToPromptDelivery &&
|
|
835
836
|
typeof request.responseFormat === 'object' &&
|
|
836
837
|
(request.responseFormat.schemaDelivery ?? 'auto') === 'auto') {
|
|
837
838
|
request.responseFormat.schemaDelivery = 'prompt';
|
|
838
839
|
promotedToPromptDelivery = true;
|
|
839
840
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
lastError = errorMessage;
|
|
851
|
-
break; // No more retries left
|
|
852
|
-
}
|
|
841
|
+
}
|
|
842
|
+
if (failure) {
|
|
843
|
+
result = undefined;
|
|
844
|
+
if (outputRetries > 0) {
|
|
845
|
+
outputRetries--;
|
|
846
|
+
yield emit({ type: 'textReset', reason: failure.reason, error: failure, request });
|
|
847
|
+
yield emitMessage({
|
|
848
|
+
role: 'user',
|
|
849
|
+
content: failure.message,
|
|
850
|
+
});
|
|
853
851
|
}
|
|
854
852
|
else {
|
|
855
|
-
|
|
853
|
+
lastError = failure;
|
|
854
|
+
break; // No more retries left
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
else if (this.input.review) {
|
|
858
|
+
// A valid result goes to the review, whose feedback asks the model
|
|
859
|
+
// to answer again.
|
|
860
|
+
const output = result;
|
|
861
|
+
const attempt = ++reviews;
|
|
862
|
+
yield emit({ type: 'reviewStart', output, attempt, request });
|
|
863
|
+
const verdict = await this.input.review(output, { input, request, ctx, attempt });
|
|
864
|
+
if ('pass' in verdict) {
|
|
865
|
+
yield emit({ type: 'reviewPass', output, attempt, request });
|
|
856
866
|
lastError = undefined;
|
|
857
867
|
break;
|
|
858
868
|
}
|
|
869
|
+
yield emit({ type: 'reviewFeedback', output, feedback: verdict.feedback, attempt, request });
|
|
870
|
+
result = undefined;
|
|
871
|
+
if (reviewRetries <= 0) {
|
|
872
|
+
lastError = new ReviewRejectedError(output, verdict.feedback);
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
reviewRetries--;
|
|
876
|
+
yield emitMessage({ role: 'user', content: verdict.feedback });
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
// A result was successfully parsed and validated!
|
|
880
|
+
lastError = undefined;
|
|
881
|
+
break;
|
|
859
882
|
}
|
|
860
883
|
}
|
|
861
884
|
}
|
|
862
885
|
catch (requestError) {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
886
|
+
const recoverable = !ctx.signal?.aborted && !streamController.signal.aborted && this.input.runtimeSchemaFallback !== false;
|
|
887
|
+
// A classified schema rejection takes the first schema fallback not
|
|
888
|
+
// yet taken for its schema that applies, and the request goes again.
|
|
889
|
+
// The step stays on the request, so later iterations start there.
|
|
890
|
+
const rejected = requestError instanceof SchemaRejectedError ? requestError : undefined;
|
|
891
|
+
const fallback = rejected && recoverable
|
|
892
|
+
? schemaFallbacks.find(step => !fallbacksTaken.some(([taken, target]) => taken === step && sameTarget(target, rejected.target)) && step.apply(request, rejected.target))
|
|
893
|
+
: undefined;
|
|
894
|
+
if (rejected && fallback) {
|
|
895
|
+
fallbacksTaken.push([fallback, rejected.target]);
|
|
896
|
+
yield emit({ type: 'schemaFallback', fallback: fallback.name, error: rejected, request });
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
if (!rejected &&
|
|
900
|
+
recoverable &&
|
|
866
901
|
!promotedToPromptDelivery &&
|
|
867
902
|
typeof request.responseFormat === 'object' &&
|
|
868
903
|
(request.responseFormat.schemaDelivery ?? 'auto') === 'auto' &&
|
|
@@ -943,6 +978,7 @@ export class Prompt {
|
|
|
943
978
|
systemMessage.content = dynamicContent;
|
|
944
979
|
request.tools = dynamicTools;
|
|
945
980
|
request.responseFormat = dynamicResponseFormat;
|
|
981
|
+
fallbacksTaken.forEach(([step, target]) => step.apply(request, target));
|
|
946
982
|
// Update toolMap with new tool objects
|
|
947
983
|
toolMap.clear();
|
|
948
984
|
if (dynamicToolObjects) {
|
|
@@ -987,13 +1023,11 @@ export class Prompt {
|
|
|
987
1023
|
if (ctx.signal?.aborted) {
|
|
988
1024
|
return undefined;
|
|
989
1025
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
throw new Error(`Prompt ${this.input.name} failed: ${lastError}`);
|
|
1026
|
+
const reason = lastError?.message ?? (iterations === maxIterations
|
|
1027
|
+
? `Maximum iterations (${maxIterations}) reached without a valid response.`
|
|
1028
|
+
: `Prompt ${this.input.name} failed without a specified error.`);
|
|
1029
|
+
const cause = lastError ?? toolFailure;
|
|
1030
|
+
throw new PromptError(`Prompt ${this.input.name} failed: ${reason}`, cause?.kind ?? 'model', cause);
|
|
997
1031
|
}
|
|
998
1032
|
yield emit({ type: 'complete', output: result, request });
|
|
999
1033
|
return result;
|
|
@@ -1057,19 +1091,16 @@ export class Prompt {
|
|
|
1057
1091
|
.filter(r => r.applicable)
|
|
1058
1092
|
.map(r => r.tool);
|
|
1059
1093
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
const
|
|
1064
|
-
?
|
|
1094
|
+
// Each tool with its compiled instructions and definition; a tool that
|
|
1095
|
+
// doesn't compile (no schema) is left out, and one without instructions
|
|
1096
|
+
// adds nothing to the tools section
|
|
1097
|
+
const compiled = selectedTools && selectedTools.length > 0
|
|
1098
|
+
? (await Promise.all(selectedTools.map(async (tool) => ({ tool, compiled: await tool.compile(ctx) }))))
|
|
1099
|
+
.flatMap(({ tool, compiled }) => compiled ? [{ tool, instructions: compiled[0], definition: compiled[1] }] : [])
|
|
1065
1100
|
: undefined;
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
// Create toolObjects as array of { tool, definition } pairs
|
|
1070
|
-
const toolObjects = selectedTools && toolInstructions
|
|
1071
|
-
? selectedTools.map((tool, i) => ({ tool, definition: toolInstructions[i][1] }))
|
|
1072
|
-
: [];
|
|
1101
|
+
const instructions = compiled?.map(c => c.instructions).filter(Boolean).join("\n\n");
|
|
1102
|
+
const tools = compiled?.map(c => c.definition);
|
|
1103
|
+
const toolObjects = compiled?.map(({ tool, definition }) => ({ tool, definition })) ?? [];
|
|
1073
1104
|
// Compute the input that is fed to the prompt's prompt content
|
|
1074
1105
|
let contentInput = input;
|
|
1075
1106
|
const translated = await this.translate(input, ctx);
|
|
@@ -1128,7 +1159,8 @@ export class Prompt {
|
|
|
1128
1159
|
* `ToolInterrupt` cutting a parallel batch short) or a tool errored
|
|
1129
1160
|
* before its content could be accumulated.
|
|
1130
1161
|
*
|
|
1131
|
-
* The marker prefix (`[aborted: …]`, `[interrupted]`, `[error: …]
|
|
1162
|
+
* The marker prefix (`[aborted: …]`, `[interrupted]`, `[error: …]`,
|
|
1163
|
+
* `[not run: …]` for a valid call skipped because a sibling was invalid)
|
|
1132
1164
|
* gives the model a clear cue and lets callers detect synthesized
|
|
1133
1165
|
* replies by inspecting the message content if they need to
|
|
1134
1166
|
* differentiate. Suspended tools are intentionally NOT routed
|
|
@@ -1140,15 +1172,18 @@ export class Prompt {
|
|
|
1140
1172
|
* result, or fall back to a model-specific instruction string.
|
|
1141
1173
|
*/
|
|
1142
1174
|
synthesizeUnpairedResult(te) {
|
|
1175
|
+
if (te.skipped) {
|
|
1176
|
+
return '[not run: another call in this batch was invalid; call it again]';
|
|
1177
|
+
}
|
|
1143
1178
|
switch (te.status) {
|
|
1144
1179
|
case 'interrupted':
|
|
1145
1180
|
return te.error
|
|
1146
|
-
? `[interrupted: ${te.error}]`
|
|
1181
|
+
? `[interrupted: ${te.error.message}]`
|
|
1147
1182
|
: '[interrupted]';
|
|
1148
1183
|
case 'error':
|
|
1149
1184
|
case 'invalid':
|
|
1150
1185
|
return te.error
|
|
1151
|
-
? `[error: ${te.error}]`
|
|
1186
|
+
? `[error: ${te.error.message}]`
|
|
1152
1187
|
: '[error]';
|
|
1153
1188
|
case 'ready':
|
|
1154
1189
|
case 'parsed':
|
|
@@ -1173,6 +1208,16 @@ export class Prompt {
|
|
|
1173
1208
|
* 2. A token estimation function is provided in the context (we can estimate token counts)
|
|
1174
1209
|
* 3. Messages already have token counts assigned (we can use these directly)
|
|
1175
1210
|
*
|
|
1211
|
+
* What is removed, oldest first, is whole groups: a message together with
|
|
1212
|
+
* the `role: 'tool'` replies that follow it, so an assistant's
|
|
1213
|
+
* `tool_calls` never lose their replies (providers reject that history).
|
|
1214
|
+
* System messages and the last user message are always kept. Groups after
|
|
1215
|
+
* the last user message (an agent's tool loop) are removed too when the
|
|
1216
|
+
* earlier history is not enough, except the most recent group.
|
|
1217
|
+
*
|
|
1218
|
+
* `protected` so a subclass can replace it, for example to summarize the
|
|
1219
|
+
* removed history instead of dropping it.
|
|
1220
|
+
*
|
|
1176
1221
|
* @param request - The original request with messages.
|
|
1177
1222
|
* @param ctx - The context containing message history and token estimation.
|
|
1178
1223
|
* @param usage - The current token usage.
|
|
@@ -1249,30 +1294,45 @@ export class Prompt {
|
|
|
1249
1294
|
return request.messages;
|
|
1250
1295
|
}
|
|
1251
1296
|
const removeTokens = totalMessageTokensFinal - maxInput;
|
|
1252
|
-
//
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
let removesRemaining = removeTokens;
|
|
1260
|
-
let messageIndex = messageMinIndex;
|
|
1261
|
-
while (removesRemaining > 0 && messageIndex < messageMaxIndex) {
|
|
1262
|
-
const message = request.messages[messageIndex];
|
|
1263
|
-
if (message.role === 'system') {
|
|
1264
|
-
trimmedMessages.push(message);
|
|
1265
|
-
messageIndex++;
|
|
1297
|
+
// Group the history: each message with the tool replies that follow it.
|
|
1298
|
+
const messages = request.messages;
|
|
1299
|
+
const groups = [];
|
|
1300
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1301
|
+
const last = groups[groups.length - 1];
|
|
1302
|
+
if (messages[i].role === 'tool' && last) {
|
|
1303
|
+
last.end = i + 1;
|
|
1266
1304
|
}
|
|
1267
1305
|
else {
|
|
1268
|
-
|
|
1269
|
-
messageIndex++;
|
|
1306
|
+
groups.push({ start: i, end: i + 1 });
|
|
1270
1307
|
}
|
|
1271
1308
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1309
|
+
const lastUser = messages.findLastIndex(m => m.role === 'user');
|
|
1310
|
+
const finalGroup = groups[groups.length - 1];
|
|
1311
|
+
const removable = (group) => {
|
|
1312
|
+
const first = messages[group.start];
|
|
1313
|
+
return first.role !== 'system' && group.start !== lastUser && group !== finalGroup;
|
|
1314
|
+
};
|
|
1315
|
+
// Oldest first: the history before the last user message, then the tool loop after it.
|
|
1316
|
+
const order = groups.filter(removable);
|
|
1317
|
+
const removed = new Set();
|
|
1318
|
+
let removesRemaining = removeTokens;
|
|
1319
|
+
for (const group of order) {
|
|
1320
|
+
if (removesRemaining <= 0)
|
|
1321
|
+
break;
|
|
1322
|
+
for (let i = group.start; i < group.end; i++) {
|
|
1323
|
+
removed.add(i);
|
|
1324
|
+
removesRemaining -= messageTokens[i] || 0;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
return messages.filter((_, i) => !removed.has(i));
|
|
1274
1328
|
}
|
|
1275
1329
|
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Whether two schema targets name the same schema.
|
|
1332
|
+
*/
|
|
1333
|
+
function sameTarget(a, b) {
|
|
1334
|
+
return a.kind === 'tool' && b.kind === 'tool' ? a.name === b.name : a.kind === b.kind;
|
|
1335
|
+
}
|
|
1276
1336
|
function once(fn) {
|
|
1277
1337
|
let promise;
|
|
1278
1338
|
return () => {
|
|
@@ -1313,7 +1373,7 @@ truncate,
|
|
|
1313
1373
|
present,
|
|
1314
1374
|
// Optional per-prompt tool interceptor (PromptInput.onToolIntercept),
|
|
1315
1375
|
// pre-bound by the loop with the matched Tool instance + ctx so this
|
|
1316
|
-
// function only needs the parsed args. Runs INSIDE run() BEFORE the tool's
|
|
1376
|
+
// function only needs the parsed args and the tool call. Runs INSIDE run() BEFORE the tool's
|
|
1317
1377
|
// own handler. A returned VALUE short-circuits and becomes `result` (the
|
|
1318
1378
|
// handler is skipped, then the normal present/success path runs). Returning
|
|
1319
1379
|
// `false`/`undefined` means "not intercepted" — the real handler runs. A
|
|
@@ -1336,7 +1396,7 @@ intercept) {
|
|
|
1336
1396
|
tool: toolInfo?.tool,
|
|
1337
1397
|
definition: toolInfo?.definition,
|
|
1338
1398
|
status: toolInfo ? 'ready' : 'error',
|
|
1339
|
-
error: toolInfo ? undefined : `Tool not found: ${toolCall.name}
|
|
1399
|
+
error: toolInfo ? undefined : new ToolInputError(toolCall.name, `Tool not found: ${toolCall.name}`),
|
|
1340
1400
|
emitStart: start.emit,
|
|
1341
1401
|
emitOutput: output.emit,
|
|
1342
1402
|
emitError: error.emit,
|
|
@@ -1367,10 +1427,6 @@ intercept) {
|
|
|
1367
1427
|
}
|
|
1368
1428
|
catch (e) {
|
|
1369
1429
|
execution.status = 'invalid';
|
|
1370
|
-
// Preserve the untruncated raw args alongside the truncated
|
|
1371
|
-
// model-facing error so the diagnostic log gets the full
|
|
1372
|
-
// payload (post-mortem reproduction is impossible without it).
|
|
1373
|
-
execution.rawArgs = args;
|
|
1374
1430
|
// Distinguish "model sent no arguments at all" from "model sent
|
|
1375
1431
|
// bad arguments". The former is usually a streaming-relay issue
|
|
1376
1432
|
// (OpenRouter/Anthropic) or a genuine model gaffe — calling it
|
|
@@ -1382,9 +1438,9 @@ intercept) {
|
|
|
1382
1438
|
? `the tool was called with NO arguments. The schema requires arguments — re-call this tool with the required fields populated. Validation: ${e.message}`
|
|
1383
1439
|
: `${e.message}, args: ${args}`;
|
|
1384
1440
|
const formatted = `Error parsing tool arguments: ${reason}`;
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1441
|
+
// The untruncated raw args go alongside the truncated model-facing
|
|
1442
|
+
// message so the diagnostic log gets the full payload.
|
|
1443
|
+
execution.error = new ToolInputError(toolCall.name, truncate ? truncate(formatted, validationErrorMaxLength) : formatted, args);
|
|
1388
1444
|
error.ready = true;
|
|
1389
1445
|
}
|
|
1390
1446
|
return execution;
|
|
@@ -1403,7 +1459,7 @@ intercept) {
|
|
|
1403
1459
|
// is intentionally NOT caught here — it flows to the catch below and
|
|
1404
1460
|
// is routed exactly like a throw from the handler (ToolInterrupt ⇒
|
|
1405
1461
|
// interrupted, PromptSuspend ⇒ suspended, else ⇒ tool error).
|
|
1406
|
-
const intercepted = intercept ? await intercept(execution.args) : false;
|
|
1462
|
+
const intercepted = intercept ? await intercept(execution.args, execution.toolCall) : false;
|
|
1407
1463
|
execution.result = intercepted === false || intercepted === undefined
|
|
1408
1464
|
? await resolve(toolInfo.tool.run(execution.args, { ...ctx, toolCallId: toolCall.id }))
|
|
1409
1465
|
: intercepted;
|
|
@@ -1420,7 +1476,7 @@ intercept) {
|
|
|
1420
1476
|
}
|
|
1421
1477
|
catch (e) {
|
|
1422
1478
|
execution.status = 'error';
|
|
1423
|
-
execution.error = `Error transforming tool result: ${e.message}
|
|
1479
|
+
execution.error = new ToolExecutionError(toolCall.name, `Error transforming tool result: ${e.message}`, e);
|
|
1424
1480
|
error.ready = true;
|
|
1425
1481
|
return execution;
|
|
1426
1482
|
}
|
|
@@ -1438,7 +1494,7 @@ intercept) {
|
|
|
1438
1494
|
}
|
|
1439
1495
|
else {
|
|
1440
1496
|
execution.status = 'error';
|
|
1441
|
-
execution.error = `Error executing tool: ${e.message}, args: ${JSON.stringify(execution.args)}
|
|
1497
|
+
execution.error = new ToolExecutionError(toolCall.name, `Error executing tool: ${e.message}, args: ${JSON.stringify(execution.args)}`, e);
|
|
1442
1498
|
error.ready = true;
|
|
1443
1499
|
}
|
|
1444
1500
|
}
|