@caupulican/pi-agent-core 0.81.40 → 0.81.42
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 +10 -5
- package/dist/agent-loop.d.ts +21 -1
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +134 -93
- package/dist/agent-loop.js.map +1 -1
- package/dist/compaction/branch-summarization.d.ts +4 -2
- package/dist/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/compaction/branch-summarization.js +4 -0
- package/dist/compaction/branch-summarization.js.map +1 -1
- package/dist/compaction/compaction.d.ts +24 -3
- package/dist/compaction/compaction.d.ts.map +1 -1
- package/dist/compaction/compaction.js +70 -32
- package/dist/compaction/compaction.js.map +1 -1
- package/dist/compaction/loop.d.ts.map +1 -1
- package/dist/compaction/loop.js +10 -5
- package/dist/compaction/loop.js.map +1 -1
- package/dist/compaction/utils.d.ts.map +1 -1
- package/dist/compaction/utils.js +3 -1
- package/dist/compaction/utils.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +1 -1
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +1 -0
- package/dist/proxy.js.map +1 -1
- package/dist/reliability/classifier.d.ts.map +1 -1
- package/dist/reliability/classifier.js +3 -3
- package/dist/reliability/classifier.js.map +1 -1
- package/dist/session/session-manager.d.ts +7 -3
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +4 -2
- package/dist/session/session-manager.js.map +1 -1
- package/dist/tool-failure-memory.d.ts +41 -0
- package/dist/tool-failure-memory.d.ts.map +1 -0
- package/dist/tool-failure-memory.js +348 -0
- package/dist/tool-failure-memory.js.map +1 -0
- package/dist/types.d.ts +37 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/usage.d.ts +5 -0
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +32 -0
- package/dist/usage.js.map +1 -1
- package/dist/uuid.d.ts +1 -1
- package/dist/uuid.d.ts.map +1 -1
- package/dist/uuid.js +1 -49
- package/dist/uuid.js.map +1 -1
- package/package.json +2 -2
package/dist/agent-loop.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Agent loop that works with AgentMessage throughout.
|
|
3
3
|
* Transforms to Message[] only at the LLM call boundary.
|
|
4
4
|
*/
|
|
5
|
-
import { EventStream,
|
|
5
|
+
import { EventStream, formatToolRepairStandingRule, streamSimple, ToolArgumentValidationError, validateToolArguments, } from "@caupulican/pi-ai";
|
|
6
|
+
import { assessToolFailure, clearToolFailure, createToolFailureMemoryTracker, createToolFailureResult, normalizeToolSignature, rememberToolFailure, sanitizeToolFailureContext, toolFailureCorrection, } from "./tool-failure-memory.js";
|
|
6
7
|
import { DEFAULT_MAX_STALL_TURNS } from "./types.js";
|
|
7
8
|
import { createEmptyUsage } from "./usage.js";
|
|
8
9
|
/**
|
|
@@ -106,18 +107,29 @@ function createAgentStream() {
|
|
|
106
107
|
*/
|
|
107
108
|
const STALL_WINDOW_PERIODS = 4;
|
|
108
109
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* clearly-volatile patterns are masked: short numbers (`file2.ts`, `line 42`, `count: 3`) are kept so
|
|
113
|
-
* genuinely-distinct calls (reading numbered files, different line ranges) are NOT falsely merged.
|
|
110
|
+
* Apply one request-local preflight without mutating persistent loop configuration.
|
|
111
|
+
* Shared with isolated tool-free provider calls so every transport boundary has identical
|
|
112
|
+
* validation and non-widening semantics.
|
|
114
113
|
*/
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
export async function resolveRequestPreflightMaxTokens(options) {
|
|
115
|
+
if (!options.requestPreflight)
|
|
116
|
+
return options.maxTokens;
|
|
117
|
+
if (options.maxTokens !== undefined && (!Number.isSafeInteger(options.maxTokens) || options.maxTokens <= 0)) {
|
|
118
|
+
throw new TypeError("request maxTokens must be a positive safe integer");
|
|
119
|
+
}
|
|
120
|
+
const preflight = await options.requestPreflight({ model: options.model, context: options.context, maxTokens: options.maxTokens }, options.signal);
|
|
121
|
+
if (preflight?.maxTokens === undefined)
|
|
122
|
+
return options.maxTokens;
|
|
123
|
+
if (!Number.isSafeInteger(preflight.maxTokens) || preflight.maxTokens <= 0) {
|
|
124
|
+
throw new TypeError("requestPreflight.maxTokens must be a positive safe integer");
|
|
125
|
+
}
|
|
126
|
+
const ceilings = [preflight.maxTokens];
|
|
127
|
+
if (options.maxTokens !== undefined)
|
|
128
|
+
ceilings.push(options.maxTokens);
|
|
129
|
+
if (Number.isSafeInteger(options.model.maxTokens) && options.model.maxTokens > 0) {
|
|
130
|
+
ceilings.push(options.model.maxTokens);
|
|
131
|
+
}
|
|
132
|
+
return Math.min(...ceilings);
|
|
121
133
|
}
|
|
122
134
|
/**
|
|
123
135
|
* Main loop logic shared by agentLoop and agentLoopContinue.
|
|
@@ -139,7 +151,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
139
151
|
const stallWindow = [];
|
|
140
152
|
const validationFailureTracker = { repeats: 0 };
|
|
141
153
|
const repairTeachTracker = new Map();
|
|
142
|
-
|
|
154
|
+
let toolFailureMemory = createToolFailureMemoryTracker(currentContext.messages);
|
|
143
155
|
// Check for steering messages at start (user may have typed while waiting)
|
|
144
156
|
let pendingMessages = (await config.getSteeringMessages?.()) || [];
|
|
145
157
|
// Outer loop: continues when queued follow-up messages arrive after agent would stop
|
|
@@ -176,7 +188,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
176
188
|
const toolResults = [];
|
|
177
189
|
hasMoreToolCalls = false;
|
|
178
190
|
if (toolCalls.length > 0) {
|
|
179
|
-
const executedToolBatch = await executeToolCalls(currentContext, message, config, validationFailureTracker, repairTeachTracker,
|
|
191
|
+
const executedToolBatch = await executeToolCalls(currentContext, message, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit);
|
|
180
192
|
toolResults.push(...executedToolBatch.messages);
|
|
181
193
|
hasMoreToolCalls = !executedToolBatch.terminate;
|
|
182
194
|
for (const result of toolResults) {
|
|
@@ -207,6 +219,9 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
207
219
|
const nextTurnSnapshot = await config.prepareNextTurn?.(nextTurnContext);
|
|
208
220
|
if (nextTurnSnapshot) {
|
|
209
221
|
currentContext = nextTurnSnapshot.context ?? currentContext;
|
|
222
|
+
if (nextTurnSnapshot.context) {
|
|
223
|
+
toolFailureMemory = createToolFailureMemoryTracker(currentContext.messages);
|
|
224
|
+
}
|
|
210
225
|
config = {
|
|
211
226
|
...config,
|
|
212
227
|
model: nextTurnSnapshot.model ?? config.model,
|
|
@@ -237,12 +252,17 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
237
252
|
await emit({ type: "agent_end", messages: newMessages });
|
|
238
253
|
}
|
|
239
254
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
255
|
+
* Start one provider request through the canonical agent-loop boundary.
|
|
256
|
+
*
|
|
257
|
+
* All callers, including host-owned tool-free finalization, receive the same failure-context
|
|
258
|
+
* sanitization, context transformation/conversion, dynamic authentication, request-local reasoning,
|
|
259
|
+
* and request preflight immediately before transport.
|
|
242
260
|
*/
|
|
243
|
-
async function
|
|
244
|
-
//
|
|
245
|
-
|
|
261
|
+
export async function startAgentProviderRequest(context, config, signal, streamFn) {
|
|
262
|
+
// Failed protocol turns never reach host transforms or provider conversion. Their bounded,
|
|
263
|
+
// unresolved state is carried separately in the system prompt until the same operation succeeds.
|
|
264
|
+
const sanitized = sanitizeToolFailureContext(context.messages, context.systemPrompt);
|
|
265
|
+
let messages = sanitized.messages;
|
|
246
266
|
if (config.transformContext) {
|
|
247
267
|
messages = await config.transformContext(messages, signal);
|
|
248
268
|
}
|
|
@@ -250,26 +270,42 @@ async function streamAssistantResponse(context, config, signal, emit, streamFn)
|
|
|
250
270
|
const llmMessages = await config.convertToLlm(messages);
|
|
251
271
|
// Build LLM context
|
|
252
272
|
const llmContext = {
|
|
253
|
-
systemPrompt:
|
|
273
|
+
systemPrompt: sanitized.systemPrompt,
|
|
254
274
|
messages: llmMessages,
|
|
255
275
|
tools: context.tools,
|
|
256
276
|
};
|
|
257
277
|
const streamFunction = streamFn || streamSimple;
|
|
258
|
-
|
|
278
|
+
const requestMaxTokens = await resolveRequestPreflightMaxTokens({
|
|
279
|
+
requestPreflight: config.requestPreflight,
|
|
280
|
+
model: config.model,
|
|
281
|
+
context: llmContext,
|
|
282
|
+
maxTokens: config.maxTokens,
|
|
283
|
+
signal,
|
|
284
|
+
});
|
|
285
|
+
// Resolve credentials only after the request-local authority/budget gate accepts the request.
|
|
286
|
+
// This prevents an already-exhausted background lane from refreshing OAuth/SSO credentials.
|
|
259
287
|
const resolvedApiKey = (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
|
|
260
288
|
const requestReasoning = config.resolveRequestReasoning
|
|
261
289
|
? config.resolveRequestReasoning(config.reasoning, {
|
|
262
290
|
model: config.model,
|
|
263
291
|
context: llmContext,
|
|
264
|
-
maxTokens:
|
|
292
|
+
maxTokens: requestMaxTokens,
|
|
265
293
|
})
|
|
266
294
|
: config.reasoning;
|
|
267
|
-
|
|
295
|
+
return await streamFunction(config.model, llmContext, {
|
|
268
296
|
...config,
|
|
269
297
|
apiKey: resolvedApiKey,
|
|
298
|
+
maxTokens: requestMaxTokens,
|
|
270
299
|
reasoning: requestReasoning,
|
|
271
300
|
signal,
|
|
272
301
|
});
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Stream an assistant response from the LLM.
|
|
305
|
+
* This is where AgentMessage[] gets transformed to Message[] for the LLM.
|
|
306
|
+
*/
|
|
307
|
+
async function streamAssistantResponse(context, config, signal, emit, streamFn) {
|
|
308
|
+
const response = await startAgentProviderRequest(context, config, signal, streamFn);
|
|
273
309
|
let partialMessage = null;
|
|
274
310
|
let addedPartial = false;
|
|
275
311
|
for await (const event of response) {
|
|
@@ -330,15 +366,15 @@ async function streamAssistantResponse(context, config, signal, emit, streamFn)
|
|
|
330
366
|
/**
|
|
331
367
|
* Execute tool calls from an assistant message.
|
|
332
368
|
*/
|
|
333
|
-
async function executeToolCalls(currentContext, assistantMessage, config, validationFailureTracker, repairTeachTracker,
|
|
369
|
+
async function executeToolCalls(currentContext, assistantMessage, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit) {
|
|
334
370
|
const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
|
|
335
371
|
const hasSequentialToolCall = toolCalls.some((tc) => currentContext.tools?.find((t) => t.name === tc.name)?.executionMode === "sequential");
|
|
336
372
|
if (config.toolExecution === "sequential" || hasSequentialToolCall) {
|
|
337
|
-
return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker,
|
|
373
|
+
return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit);
|
|
338
374
|
}
|
|
339
|
-
return executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker,
|
|
375
|
+
return executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit);
|
|
340
376
|
}
|
|
341
|
-
async function executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker,
|
|
377
|
+
async function executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit) {
|
|
342
378
|
const finalizedCalls = [];
|
|
343
379
|
const messages = [];
|
|
344
380
|
for (const toolCall of toolCalls) {
|
|
@@ -346,17 +382,12 @@ async function executeToolCallsSequential(currentContext, assistantMessage, tool
|
|
|
346
382
|
await emitToolExecutionStart(toolCall, emit);
|
|
347
383
|
let finalized;
|
|
348
384
|
if (preparation.kind === "immediate") {
|
|
349
|
-
resetExecutionFailureTracker(executionFailureTracker);
|
|
350
385
|
emitToolArgumentValidationTelemetry(config, preparation.validationEvent, "not_run", "none");
|
|
351
|
-
finalized =
|
|
352
|
-
toolCall,
|
|
353
|
-
result: preparation.result,
|
|
354
|
-
isError: preparation.isError,
|
|
355
|
-
};
|
|
386
|
+
finalized = finalizeRejectedToolCall(toolCall, preparation, toolFailureMemory);
|
|
356
387
|
}
|
|
357
388
|
else {
|
|
358
389
|
const executed = await executePreparedToolCall(preparation, signal, emit);
|
|
359
|
-
finalized = await finalizeExecutedToolCall(currentContext, assistantMessage, preparation, executed, config, repairTeachTracker,
|
|
390
|
+
finalized = await finalizeExecutedToolCall(currentContext, assistantMessage, preparation, executed, config, repairTeachTracker, toolFailureMemory, signal);
|
|
360
391
|
}
|
|
361
392
|
await emitToolExecutionEnd(finalized, emit);
|
|
362
393
|
const toolResultMessage = createToolResultMessage(finalized);
|
|
@@ -372,19 +403,14 @@ async function executeToolCallsSequential(currentContext, assistantMessage, tool
|
|
|
372
403
|
terminate: shouldTerminateToolBatch(finalizedCalls),
|
|
373
404
|
};
|
|
374
405
|
}
|
|
375
|
-
async function executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker,
|
|
406
|
+
async function executeToolCallsParallel(currentContext, assistantMessage, toolCalls, config, validationFailureTracker, repairTeachTracker, toolFailureMemory, signal, emit) {
|
|
376
407
|
const finalizedCalls = [];
|
|
377
408
|
for (const toolCall of toolCalls) {
|
|
378
409
|
const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, validationFailureTracker, signal);
|
|
379
410
|
await emitToolExecutionStart(toolCall, emit);
|
|
380
411
|
if (preparation.kind === "immediate") {
|
|
381
|
-
resetExecutionFailureTracker(executionFailureTracker);
|
|
382
412
|
emitToolArgumentValidationTelemetry(config, preparation.validationEvent, "not_run", "none");
|
|
383
|
-
const finalized =
|
|
384
|
-
toolCall,
|
|
385
|
-
result: preparation.result,
|
|
386
|
-
isError: preparation.isError,
|
|
387
|
-
};
|
|
413
|
+
const finalized = finalizeRejectedToolCall(toolCall, preparation, toolFailureMemory);
|
|
388
414
|
await emitToolExecutionEnd(finalized, emit);
|
|
389
415
|
finalizedCalls.push(finalized);
|
|
390
416
|
if (signal?.aborted) {
|
|
@@ -394,7 +420,7 @@ async function executeToolCallsParallel(currentContext, assistantMessage, toolCa
|
|
|
394
420
|
}
|
|
395
421
|
finalizedCalls.push(async () => {
|
|
396
422
|
const executed = await executePreparedToolCall(preparation, signal, emit);
|
|
397
|
-
const finalized = await finalizeExecutedToolCall(currentContext, assistantMessage, preparation, executed, config, repairTeachTracker,
|
|
423
|
+
const finalized = await finalizeExecutedToolCall(currentContext, assistantMessage, preparation, executed, config, repairTeachTracker, toolFailureMemory, signal);
|
|
398
424
|
await emitToolExecutionEnd(finalized, emit);
|
|
399
425
|
return finalized;
|
|
400
426
|
});
|
|
@@ -416,7 +442,6 @@ async function executeToolCallsParallel(currentContext, assistantMessage, toolCa
|
|
|
416
442
|
}
|
|
417
443
|
const DEFAULT_TOOL_VALIDATION_ESCALATION_THRESHOLD = 3;
|
|
418
444
|
const TOOL_REPAIR_TEACH_EVERY = 5;
|
|
419
|
-
const TOOL_EXECUTION_FAILURE_TEACH_AT = 2;
|
|
420
445
|
function shouldTerminateToolBatch(finalizedCalls) {
|
|
421
446
|
return finalizedCalls.length > 0 && finalizedCalls.every((finalized) => finalized.result.terminate === true);
|
|
422
447
|
}
|
|
@@ -447,6 +472,20 @@ function createValidationBounceTelemetry(config, toolCall, errorKeyword) {
|
|
|
447
472
|
executionOutcome: "not_run",
|
|
448
473
|
};
|
|
449
474
|
}
|
|
475
|
+
function validationFailureCorrection(event, toolName) {
|
|
476
|
+
const shape = event?.failureShape
|
|
477
|
+
?.slice(0, 3)
|
|
478
|
+
.map((entry) => `${entry.path}: expected ${entry.expectedType}, received ${entry.receivedType}`)
|
|
479
|
+
.join("; ");
|
|
480
|
+
const rules = [
|
|
481
|
+
...new Set((event?.failureModes ?? [])
|
|
482
|
+
.filter((mode) => mode !== "other")
|
|
483
|
+
.map((mode) => formatToolRepairStandingRule(mode))),
|
|
484
|
+
];
|
|
485
|
+
return [`Match ${toolName} arguments to its current schema.`, shape ? `Fix ${shape}.` : undefined, ...rules]
|
|
486
|
+
.filter((part) => part !== undefined)
|
|
487
|
+
.join(" ");
|
|
488
|
+
}
|
|
450
489
|
function resetValidationFailureTracker(tracker) {
|
|
451
490
|
tracker.signature = undefined;
|
|
452
491
|
tracker.repeats = 0;
|
|
@@ -503,6 +542,8 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
503
542
|
kind: "immediate",
|
|
504
543
|
result: createErrorToolResult(toolCall.errorMessage),
|
|
505
544
|
isError: true,
|
|
545
|
+
failureCode: "malformed_call",
|
|
546
|
+
correction: "Resend one complete JSON argument object matching the current tool schema.",
|
|
506
547
|
validationEvent: createValidationBounceTelemetry(config, toolCall, "unknown_tool"),
|
|
507
548
|
};
|
|
508
549
|
}
|
|
@@ -512,6 +553,8 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
512
553
|
kind: "immediate",
|
|
513
554
|
result: createErrorToolResult(`Tool ${toolCall.name} not found`),
|
|
514
555
|
isError: true,
|
|
556
|
+
failureCode: "unknown_tool",
|
|
557
|
+
correction: "Choose a tool from the currently available tool list.",
|
|
515
558
|
validationEvent: createValidationBounceTelemetry(config, toolCall, "unknown_tool"),
|
|
516
559
|
};
|
|
517
560
|
}
|
|
@@ -546,14 +589,20 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
546
589
|
kind: "immediate",
|
|
547
590
|
result: createErrorToolResult("Operation aborted"),
|
|
548
591
|
isError: true,
|
|
592
|
+
failureCode: "aborted",
|
|
593
|
+
correction: "Retry only if the operation is still required.",
|
|
549
594
|
validationEvent,
|
|
550
595
|
};
|
|
551
596
|
}
|
|
552
597
|
if (beforeResult?.block) {
|
|
598
|
+
const reason = beforeResult.reason || "Tool execution was blocked";
|
|
553
599
|
return {
|
|
554
600
|
kind: "immediate",
|
|
555
|
-
result: createErrorToolResult(
|
|
601
|
+
result: createErrorToolResult(reason),
|
|
556
602
|
isError: true,
|
|
603
|
+
failureCode: "blocked",
|
|
604
|
+
correction: "Choose an allowed approach or request the required authority before retrying.",
|
|
605
|
+
diagnostic: reason,
|
|
557
606
|
validationEvent,
|
|
558
607
|
};
|
|
559
608
|
}
|
|
@@ -563,6 +612,8 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
563
612
|
kind: "immediate",
|
|
564
613
|
result: createErrorToolResult("Operation aborted"),
|
|
565
614
|
isError: true,
|
|
615
|
+
failureCode: "aborted",
|
|
616
|
+
correction: "Retry only if the operation is still required.",
|
|
566
617
|
validationEvent,
|
|
567
618
|
};
|
|
568
619
|
}
|
|
@@ -584,10 +635,22 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
584
635
|
kind: "immediate",
|
|
585
636
|
result: createErrorToolResult(message),
|
|
586
637
|
isError: true,
|
|
638
|
+
failureCode: isToolArgumentValidationError(error) ? "invalid_arguments" : "preflight_error",
|
|
639
|
+
correction: isToolArgumentValidationError(error)
|
|
640
|
+
? validationFailureCorrection(validationEvent, toolCall.name)
|
|
641
|
+
: toolFailureCorrection(message, "rejected"),
|
|
587
642
|
validationEvent,
|
|
588
643
|
};
|
|
589
644
|
}
|
|
590
645
|
}
|
|
646
|
+
function finalizeRejectedToolCall(toolCall, outcome, tracker) {
|
|
647
|
+
const record = rememberToolFailure(tracker, toolCall.name, toolCall.arguments, "rejected", outcome.failureCode, outcome.correction, outcome.diagnostic);
|
|
648
|
+
return {
|
|
649
|
+
toolCall,
|
|
650
|
+
result: createToolFailureResult(record, outcome.result.terminate),
|
|
651
|
+
isError: true,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
591
654
|
async function executePreparedToolCall(prepared, signal, emit) {
|
|
592
655
|
const updateEvents = [];
|
|
593
656
|
try {
|
|
@@ -601,15 +664,23 @@ async function executePreparedToolCall(prepared, signal, emit) {
|
|
|
601
664
|
})));
|
|
602
665
|
});
|
|
603
666
|
await Promise.all(updateEvents);
|
|
604
|
-
return {
|
|
667
|
+
return {
|
|
668
|
+
result,
|
|
669
|
+
// Tool definitions can report an expected operation failure without
|
|
670
|
+
// throwing. Keep the returned result intact through afterToolCall so
|
|
671
|
+
// policy hooks can inspect its bounded diagnostics and metadata.
|
|
672
|
+
isError: result.isError === true,
|
|
673
|
+
...(result.isError === true ? { errorClass: "tool_result_error" } : {}),
|
|
674
|
+
};
|
|
605
675
|
}
|
|
606
676
|
catch (error) {
|
|
607
677
|
await Promise.all(updateEvents);
|
|
608
678
|
const message = error instanceof Error ? error.message : String(error);
|
|
609
679
|
return {
|
|
610
|
-
result:
|
|
680
|
+
result: createErrorToolResult(message),
|
|
611
681
|
isError: true,
|
|
612
682
|
errorClass: error instanceof Error ? error.name : typeof error,
|
|
683
|
+
failureMessage: message,
|
|
613
684
|
};
|
|
614
685
|
}
|
|
615
686
|
}
|
|
@@ -637,39 +708,11 @@ function appendRepairTeachNotes(result, toolCall, tracker, config) {
|
|
|
637
708
|
taught: true,
|
|
638
709
|
};
|
|
639
710
|
}
|
|
640
|
-
function
|
|
641
|
-
tracker.signature = undefined;
|
|
642
|
-
tracker.repeats = 0;
|
|
643
|
-
}
|
|
644
|
-
function executionFailureSignature(prepared, errorClass) {
|
|
645
|
-
return `${normalizeToolSignature([[prepared.toolCall.name, prepared.args]])}\0${errorClass}`;
|
|
646
|
-
}
|
|
647
|
-
function appendExecutionFailureTeachNote(result, prepared, errorClass, tracker) {
|
|
648
|
-
const signature = executionFailureSignature(prepared, errorClass ?? "tool-error");
|
|
649
|
-
if (tracker.signature === signature) {
|
|
650
|
-
tracker.repeats++;
|
|
651
|
-
}
|
|
652
|
-
else {
|
|
653
|
-
tracker.signature = signature;
|
|
654
|
-
tracker.repeats = 1;
|
|
655
|
-
}
|
|
656
|
-
if (tracker.repeats !== TOOL_EXECUTION_FAILURE_TEACH_AT || tracker.taughtSignature === signature)
|
|
657
|
-
return result;
|
|
658
|
-
tracker.taughtSignature = signature;
|
|
659
|
-
return {
|
|
660
|
-
...result,
|
|
661
|
-
content: [
|
|
662
|
-
{
|
|
663
|
-
type: "text",
|
|
664
|
-
text: `[harness] This exact ${prepared.toolCall.name} call failed twice with the same ${errorClass ?? "tool"} error. Change the arguments or approach; do not resend the identical call.`,
|
|
665
|
-
},
|
|
666
|
-
...result.content,
|
|
667
|
-
],
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
async function finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, repairTeachTracker, executionFailureTracker, signal) {
|
|
711
|
+
async function finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, repairTeachTracker, toolFailureMemory, signal) {
|
|
671
712
|
let result = executed.result;
|
|
672
713
|
let isError = executed.isError;
|
|
714
|
+
let failureMessage = executed.failureMessage ?? "";
|
|
715
|
+
let errorClass = executed.errorClass;
|
|
673
716
|
if (config.afterToolCall) {
|
|
674
717
|
try {
|
|
675
718
|
const afterResult = await config.afterToolCall({
|
|
@@ -684,23 +727,32 @@ async function finalizeExecutedToolCall(currentContext, assistantMessage, prepar
|
|
|
684
727
|
result = {
|
|
685
728
|
content: afterResult.content ?? result.content,
|
|
686
729
|
details: afterResult.details ?? result.details,
|
|
730
|
+
usage: afterResult.usage ?? result.usage,
|
|
687
731
|
terminate: afterResult.terminate ?? result.terminate,
|
|
688
732
|
};
|
|
689
733
|
isError = afterResult.isError ?? isError;
|
|
690
734
|
}
|
|
691
735
|
}
|
|
692
736
|
catch (error) {
|
|
693
|
-
|
|
737
|
+
failureMessage = error instanceof Error ? error.message : String(error);
|
|
738
|
+
errorClass = error instanceof Error ? error.name : typeof error;
|
|
739
|
+
result = { ...createErrorToolResult(failureMessage), usage: result.usage };
|
|
694
740
|
isError = true;
|
|
695
741
|
}
|
|
696
742
|
}
|
|
697
743
|
if (isError) {
|
|
698
|
-
|
|
744
|
+
const usage = result.usage;
|
|
745
|
+
const effectiveFailureMessage = failureMessage || result.content.find((block) => block.type === "text")?.text || "Tool execution failed";
|
|
746
|
+
const assessment = assessToolFailure(effectiveFailureMessage, "failed", errorClass);
|
|
747
|
+
const record = rememberToolFailure(toolFailureMemory, prepared.toolCall.name, prepared.args, "failed", assessment.failureCode, assessment.guidance, assessment.diagnostic);
|
|
748
|
+
result = { ...createToolFailureResult(record, result.terminate), usage };
|
|
699
749
|
}
|
|
700
750
|
else {
|
|
701
|
-
|
|
751
|
+
clearToolFailure(toolFailureMemory, prepared.toolCall.name, prepared.args);
|
|
702
752
|
}
|
|
703
|
-
const repaired =
|
|
753
|
+
const repaired = isError
|
|
754
|
+
? { result, taught: false }
|
|
755
|
+
: appendRepairTeachNotes(result, prepared.toolCall, repairTeachTracker, config);
|
|
704
756
|
emitToolArgumentValidationTelemetry(config, prepared.validationEvent, isError ? "failed" : "succeeded", repaired.taught ? "note" : "none");
|
|
705
757
|
return {
|
|
706
758
|
toolCall: prepared.toolCall,
|
|
@@ -714,18 +766,6 @@ function createErrorToolResult(message) {
|
|
|
714
766
|
details: {},
|
|
715
767
|
};
|
|
716
768
|
}
|
|
717
|
-
function createErrorToolResultWithGuidance(message) {
|
|
718
|
-
const guidance = getToolExecutionErrorGuidance(message);
|
|
719
|
-
if (!guidance)
|
|
720
|
-
return createErrorToolResult(message);
|
|
721
|
-
return {
|
|
722
|
-
content: [
|
|
723
|
-
{ type: "text", text: message },
|
|
724
|
-
{ type: "text", text: `[harness] ${guidance}` },
|
|
725
|
-
],
|
|
726
|
-
details: {},
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
769
|
async function emitToolExecutionStart(toolCall, emit) {
|
|
730
770
|
await emit({
|
|
731
771
|
type: "tool_execution_start",
|
|
@@ -761,6 +801,7 @@ function createToolResultMessage(finalized) {
|
|
|
761
801
|
toolName: finalized.toolCall.name,
|
|
762
802
|
content: finalized.result.content,
|
|
763
803
|
details: finalized.result.details,
|
|
804
|
+
usage: finalized.result.usage,
|
|
764
805
|
isError: finalized.isError,
|
|
765
806
|
timestamp: Date.now(),
|
|
766
807
|
};
|