@prestyj/agent 5.12.0 → 5.16.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/dist/index.cjs +155 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -2
- package/dist/index.d.ts +23 -2
- package/dist/index.js +153 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -55,6 +55,13 @@ interface AgentToolCallEndEvent {
|
|
|
55
55
|
details?: unknown;
|
|
56
56
|
isError: boolean;
|
|
57
57
|
durationMs: number;
|
|
58
|
+
/**
|
|
59
|
+
* Set only when the call failed schema validation: how many consecutive
|
|
60
|
+
* times this tool produced this same validation error. 1 means the model
|
|
61
|
+
* still has room to self-correct; 3 is the threshold that ends the turn.
|
|
62
|
+
* Logged so a retry loop shows up as a count instead of identical lines.
|
|
63
|
+
*/
|
|
64
|
+
invalidArgAttempt?: number;
|
|
58
65
|
}
|
|
59
66
|
interface AgentTurnTiming {
|
|
60
67
|
/** Logical turn start, before context transforms or provider retries. Unix epoch milliseconds. */
|
|
@@ -133,7 +140,7 @@ interface AgentTurnBudgetExtendedEvent {
|
|
|
133
140
|
*/
|
|
134
141
|
interface AgentTruncatedEvent {
|
|
135
142
|
type: "truncated";
|
|
136
|
-
reason: "max_tokens" | "refusal" | "provider_error";
|
|
143
|
+
reason: "max_tokens" | "refusal" | "provider_error" | "empty_response";
|
|
137
144
|
/** True when the loop injected a continuation and will keep going. */
|
|
138
145
|
continued: boolean;
|
|
139
146
|
}
|
|
@@ -394,6 +401,20 @@ declare function isBillingError(err: unknown): boolean;
|
|
|
394
401
|
*/
|
|
395
402
|
declare function isUsageLimitError(err: unknown): boolean;
|
|
396
403
|
declare function agentLoop(messages: Message[], options: AgentOptions): AsyncGenerator<AgentEvent, AgentResult>;
|
|
404
|
+
/**
|
|
405
|
+
* A tool call that never reached its tool. Nothing ran, so nothing changed.
|
|
406
|
+
*/
|
|
407
|
+
declare function cancelledBeforeStartText(name: string): string;
|
|
408
|
+
/**
|
|
409
|
+
* A tool call that started running and was cut off before reporting back.
|
|
410
|
+
*
|
|
411
|
+
* The distinction from {@link cancelledBeforeStartText} is the whole point: a
|
|
412
|
+
* dispatched `git push`, deploy or MCP call may well have COMPLETED before the
|
|
413
|
+
* abort landed. Telling the model it was "interrupted" reads as "it did not
|
|
414
|
+
* happen", so the model repeats the side effect — or reports to the user that
|
|
415
|
+
* something never ran when it did.
|
|
416
|
+
*/
|
|
417
|
+
declare function indeterminateOutcomeText(name: string): string;
|
|
397
418
|
|
|
398
419
|
/**
|
|
399
420
|
* A local inference server (llama.cpp, vLLM, Ollama, LM Studio) can spend
|
|
@@ -404,4 +425,4 @@ declare function agentLoop(messages: Message[], options: AgentOptions): AsyncGen
|
|
|
404
425
|
*/
|
|
405
426
|
declare function isLocalBackendUrl(baseUrl?: string): boolean;
|
|
406
427
|
|
|
407
|
-
export { Agent, type AgentDoneEvent, type AgentErrorEvent, type AgentEvent, type AgentFollowUpMessageEvent, type AgentOptions, type AgentResult, type AgentRetryEvent, type AgentServerToolCallEvent, type AgentServerToolResultEvent, type AgentSteeringMessageEvent, AgentStream, type AgentTextDeltaEvent, type AgentThinkingDeltaEvent, type AgentTool, type AgentToolCallDeltaEvent, type AgentToolCallEndEvent, type AgentToolCallStartEvent, type AgentToolCallUpdateEvent, type AgentTurnEndEvent, type AgentTurnTiming, type StreamDiagnosticFn, type StructuredToolResult, type ToolContext, type ToolExecuteResult, type ToolExecutionMode, type TransformContextOptions, agentLoop, isAbortError, isBillingError, isContextOverflow, isLocalBackendUrl, isUsageLimitError, setStreamDiagnostic };
|
|
428
|
+
export { Agent, type AgentDoneEvent, type AgentErrorEvent, type AgentEvent, type AgentFollowUpMessageEvent, type AgentOptions, type AgentResult, type AgentRetryEvent, type AgentServerToolCallEvent, type AgentServerToolResultEvent, type AgentSteeringMessageEvent, AgentStream, type AgentTextDeltaEvent, type AgentThinkingDeltaEvent, type AgentTool, type AgentToolCallDeltaEvent, type AgentToolCallEndEvent, type AgentToolCallStartEvent, type AgentToolCallUpdateEvent, type AgentTurnEndEvent, type AgentTurnTiming, type StreamDiagnosticFn, type StructuredToolResult, type ToolContext, type ToolExecuteResult, type ToolExecutionMode, type TransformContextOptions, agentLoop, cancelledBeforeStartText, indeterminateOutcomeText, isAbortError, isBillingError, isContextOverflow, isLocalBackendUrl, isUsageLimitError, setStreamDiagnostic };
|
package/dist/index.d.ts
CHANGED
|
@@ -55,6 +55,13 @@ interface AgentToolCallEndEvent {
|
|
|
55
55
|
details?: unknown;
|
|
56
56
|
isError: boolean;
|
|
57
57
|
durationMs: number;
|
|
58
|
+
/**
|
|
59
|
+
* Set only when the call failed schema validation: how many consecutive
|
|
60
|
+
* times this tool produced this same validation error. 1 means the model
|
|
61
|
+
* still has room to self-correct; 3 is the threshold that ends the turn.
|
|
62
|
+
* Logged so a retry loop shows up as a count instead of identical lines.
|
|
63
|
+
*/
|
|
64
|
+
invalidArgAttempt?: number;
|
|
58
65
|
}
|
|
59
66
|
interface AgentTurnTiming {
|
|
60
67
|
/** Logical turn start, before context transforms or provider retries. Unix epoch milliseconds. */
|
|
@@ -133,7 +140,7 @@ interface AgentTurnBudgetExtendedEvent {
|
|
|
133
140
|
*/
|
|
134
141
|
interface AgentTruncatedEvent {
|
|
135
142
|
type: "truncated";
|
|
136
|
-
reason: "max_tokens" | "refusal" | "provider_error";
|
|
143
|
+
reason: "max_tokens" | "refusal" | "provider_error" | "empty_response";
|
|
137
144
|
/** True when the loop injected a continuation and will keep going. */
|
|
138
145
|
continued: boolean;
|
|
139
146
|
}
|
|
@@ -394,6 +401,20 @@ declare function isBillingError(err: unknown): boolean;
|
|
|
394
401
|
*/
|
|
395
402
|
declare function isUsageLimitError(err: unknown): boolean;
|
|
396
403
|
declare function agentLoop(messages: Message[], options: AgentOptions): AsyncGenerator<AgentEvent, AgentResult>;
|
|
404
|
+
/**
|
|
405
|
+
* A tool call that never reached its tool. Nothing ran, so nothing changed.
|
|
406
|
+
*/
|
|
407
|
+
declare function cancelledBeforeStartText(name: string): string;
|
|
408
|
+
/**
|
|
409
|
+
* A tool call that started running and was cut off before reporting back.
|
|
410
|
+
*
|
|
411
|
+
* The distinction from {@link cancelledBeforeStartText} is the whole point: a
|
|
412
|
+
* dispatched `git push`, deploy or MCP call may well have COMPLETED before the
|
|
413
|
+
* abort landed. Telling the model it was "interrupted" reads as "it did not
|
|
414
|
+
* happen", so the model repeats the side effect — or reports to the user that
|
|
415
|
+
* something never ran when it did.
|
|
416
|
+
*/
|
|
417
|
+
declare function indeterminateOutcomeText(name: string): string;
|
|
397
418
|
|
|
398
419
|
/**
|
|
399
420
|
* A local inference server (llama.cpp, vLLM, Ollama, LM Studio) can spend
|
|
@@ -404,4 +425,4 @@ declare function agentLoop(messages: Message[], options: AgentOptions): AsyncGen
|
|
|
404
425
|
*/
|
|
405
426
|
declare function isLocalBackendUrl(baseUrl?: string): boolean;
|
|
406
427
|
|
|
407
|
-
export { Agent, type AgentDoneEvent, type AgentErrorEvent, type AgentEvent, type AgentFollowUpMessageEvent, type AgentOptions, type AgentResult, type AgentRetryEvent, type AgentServerToolCallEvent, type AgentServerToolResultEvent, type AgentSteeringMessageEvent, AgentStream, type AgentTextDeltaEvent, type AgentThinkingDeltaEvent, type AgentTool, type AgentToolCallDeltaEvent, type AgentToolCallEndEvent, type AgentToolCallStartEvent, type AgentToolCallUpdateEvent, type AgentTurnEndEvent, type AgentTurnTiming, type StreamDiagnosticFn, type StructuredToolResult, type ToolContext, type ToolExecuteResult, type ToolExecutionMode, type TransformContextOptions, agentLoop, isAbortError, isBillingError, isContextOverflow, isLocalBackendUrl, isUsageLimitError, setStreamDiagnostic };
|
|
428
|
+
export { Agent, type AgentDoneEvent, type AgentErrorEvent, type AgentEvent, type AgentFollowUpMessageEvent, type AgentOptions, type AgentResult, type AgentRetryEvent, type AgentServerToolCallEvent, type AgentServerToolResultEvent, type AgentSteeringMessageEvent, AgentStream, type AgentTextDeltaEvent, type AgentThinkingDeltaEvent, type AgentTool, type AgentToolCallDeltaEvent, type AgentToolCallEndEvent, type AgentToolCallStartEvent, type AgentToolCallUpdateEvent, type AgentTurnEndEvent, type AgentTurnTiming, type StreamDiagnosticFn, type StructuredToolResult, type ToolContext, type ToolExecuteResult, type ToolExecutionMode, type TransformContextOptions, agentLoop, cancelledBeforeStartText, indeterminateOutcomeText, isAbortError, isBillingError, isContextOverflow, isLocalBackendUrl, isUsageLimitError, setStreamDiagnostic };
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,60 @@ function isLocalBackendUrl(baseUrl) {
|
|
|
35
35
|
return false;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// src/output-ceiling.ts
|
|
39
|
+
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
40
|
+
var MIN_PLAUSIBLE_CEILING = 256;
|
|
41
|
+
var MAX_PLAUSIBLE_CEILING = 1e7;
|
|
42
|
+
var ceilings = /* @__PURE__ */ new Map();
|
|
43
|
+
function outputRouteKey(route) {
|
|
44
|
+
return `${route.provider}\0${route.baseUrl ?? "default"}\0${route.model}`;
|
|
45
|
+
}
|
|
46
|
+
function parseOutputTokenCeiling(err) {
|
|
47
|
+
if (!(err instanceof Error)) return null;
|
|
48
|
+
const msg = err.message;
|
|
49
|
+
if (!/max_tokens|max output tokens|output tokens|completion tokens/i.test(msg)) return null;
|
|
50
|
+
if (err.statusCode === 402) return null;
|
|
51
|
+
if (/credit|billing|payment|insufficient|balance|quota/i.test(msg)) return null;
|
|
52
|
+
const patterns = [
|
|
53
|
+
// Anthropic: "max_tokens: 100000 > 64000, which is the maximum allowed…"
|
|
54
|
+
/max_tokens:\s*\d+\s*>\s*(\d+)/i,
|
|
55
|
+
// OpenAI: "…this model supports at most 16384 completion tokens"
|
|
56
|
+
/(?:at most|maximum of|limit of)\s+([\d,_]+)\s*(?:output|completion)?\s*tokens/i,
|
|
57
|
+
// Generic: "max output tokens is 8192" / "max_tokens must be <= 4096"
|
|
58
|
+
/(?:max_tokens|max output tokens)\b[^\d]{0,30}?([\d,_]+)/i
|
|
59
|
+
];
|
|
60
|
+
for (const pattern of patterns) {
|
|
61
|
+
const raw = msg.match(pattern)?.[1];
|
|
62
|
+
if (raw === void 0) continue;
|
|
63
|
+
const limit = Number(raw.replace(/[,_]/g, ""));
|
|
64
|
+
if (Number.isFinite(limit) && limit >= MIN_PLAUSIBLE_CEILING && limit <= MAX_PLAUSIBLE_CEILING) {
|
|
65
|
+
return limit;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function rememberOutputCeiling(key, limit) {
|
|
71
|
+
const known = outputTokenCeiling(key);
|
|
72
|
+
ceilings.set(key, {
|
|
73
|
+
limit: known === void 0 ? limit : Math.min(known, limit),
|
|
74
|
+
expiresAt: Date.now() + TTL_MS
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function outputTokenCeiling(key) {
|
|
78
|
+
const entry = ceilings.get(key);
|
|
79
|
+
if (!entry) return void 0;
|
|
80
|
+
if (entry.expiresAt <= Date.now()) {
|
|
81
|
+
ceilings.delete(key);
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
return entry.limit;
|
|
85
|
+
}
|
|
86
|
+
function clampOutputTokens(key, requested) {
|
|
87
|
+
const ceiling = outputTokenCeiling(key);
|
|
88
|
+
if (ceiling === void 0) return requested;
|
|
89
|
+
return requested === void 0 ? ceiling : Math.min(requested, ceiling);
|
|
90
|
+
}
|
|
91
|
+
|
|
38
92
|
// src/agent-loop.ts
|
|
39
93
|
var DEFAULT_MAX_TURNS = 300;
|
|
40
94
|
var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
|
|
@@ -319,6 +373,16 @@ async function* agentLoop(messages, options) {
|
|
|
319
373
|
const MAX_OUTPUT_CONTINUATIONS = 2;
|
|
320
374
|
const MAX_TOKENS_CONTINUATION_PROMPT = "[Your previous response hit the output-token limit and was cut off. The text above is what was already delivered to the user. Continue exactly from where it stopped \u2014 do not repeat or restart it.]";
|
|
321
375
|
let maxTokensContinuations = 0;
|
|
376
|
+
let providerCalls = 0;
|
|
377
|
+
let nonStreamingCalls = 0;
|
|
378
|
+
let warnedNonStreaming = false;
|
|
379
|
+
const MAX_OUTPUT_CEILING_RETRIES = 1;
|
|
380
|
+
let outputCeilingRetries = 0;
|
|
381
|
+
const ceilingKey = outputRouteKey({
|
|
382
|
+
provider: options.provider,
|
|
383
|
+
model: options.model,
|
|
384
|
+
baseUrl: options.baseUrl
|
|
385
|
+
});
|
|
322
386
|
const OVERLOAD_BASE_DELAY_MS = 2e3;
|
|
323
387
|
const OVERLOAD_MAX_DELAY_MS = 3e4;
|
|
324
388
|
const STREAM_FIRST_EVENT_TIMEOUT_MS = 45e3;
|
|
@@ -448,6 +512,18 @@ async function* agentLoop(messages, options) {
|
|
|
448
512
|
let streamIterator = null;
|
|
449
513
|
try {
|
|
450
514
|
diag("stream_call", { nonStreaming: useNonStreamingFallback });
|
|
515
|
+
providerCalls++;
|
|
516
|
+
if (useNonStreamingFallback) nonStreamingCalls++;
|
|
517
|
+
if (!warnedNonStreaming && nonStreamingCalls >= 3) {
|
|
518
|
+
warnedNonStreaming = true;
|
|
519
|
+
diag("non_streaming_session", {
|
|
520
|
+
nonStreamingCalls,
|
|
521
|
+
providerCalls,
|
|
522
|
+
provider: options.provider,
|
|
523
|
+
model: options.model,
|
|
524
|
+
impact: "streaming is disabled for this session after repeated stalls: failed turns are re-billed in full instead of resuming from partial output, and replies appear only when complete"
|
|
525
|
+
});
|
|
526
|
+
}
|
|
451
527
|
streamCallStart = Date.now();
|
|
452
528
|
providerAttemptStartedAt = streamCallStart;
|
|
453
529
|
let liveApiKey = options.apiKey;
|
|
@@ -473,7 +549,9 @@ async function* agentLoop(messages, options) {
|
|
|
473
549
|
serverTools: options.serverTools,
|
|
474
550
|
toolChoice: options.toolChoice,
|
|
475
551
|
webSearch: options.webSearch,
|
|
476
|
-
|
|
552
|
+
// Clamped to whatever ceiling this route has already rejected us for
|
|
553
|
+
// (identity when nothing has been learned).
|
|
554
|
+
maxTokens: clampOutputTokens(ceilingKey, options.maxTokens),
|
|
477
555
|
temperature: options.temperature,
|
|
478
556
|
thinking: options.thinking,
|
|
479
557
|
apiKey: liveApiKey,
|
|
@@ -630,6 +708,29 @@ async function* agentLoop(messages, options) {
|
|
|
630
708
|
});
|
|
631
709
|
throw err;
|
|
632
710
|
}
|
|
711
|
+
const statedCeiling = parseOutputTokenCeiling(err);
|
|
712
|
+
if (statedCeiling !== null) {
|
|
713
|
+
rememberOutputCeiling(ceilingKey, statedCeiling);
|
|
714
|
+
diag("output_ceiling_learned", {
|
|
715
|
+
ceiling: statedCeiling,
|
|
716
|
+
requested: options.maxTokens,
|
|
717
|
+
provider: options.provider,
|
|
718
|
+
model: options.model
|
|
719
|
+
});
|
|
720
|
+
if (outputCeilingRetries < MAX_OUTPUT_CEILING_RETRIES) {
|
|
721
|
+
outputCeilingRetries++;
|
|
722
|
+
yield {
|
|
723
|
+
type: "retry",
|
|
724
|
+
reason: "provider_error",
|
|
725
|
+
attempt: outputCeilingRetries,
|
|
726
|
+
maxAttempts: MAX_OUTPUT_CEILING_RETRIES,
|
|
727
|
+
delayMs: 0,
|
|
728
|
+
silent: true
|
|
729
|
+
};
|
|
730
|
+
turn--;
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
633
734
|
if (isContextOverflow(err)) {
|
|
634
735
|
const overflowDetails = extractContextOverflowDetails(err);
|
|
635
736
|
diag("context_overflow_detected", {
|
|
@@ -925,6 +1026,7 @@ async function* agentLoop(messages, options) {
|
|
|
925
1026
|
continue;
|
|
926
1027
|
}
|
|
927
1028
|
}
|
|
1029
|
+
const emptyExhausted = !hasActionableContent;
|
|
928
1030
|
emptyResponseRetries = 0;
|
|
929
1031
|
useNonStreamingFallback = false;
|
|
930
1032
|
totalUsage.inputTokens += response.usage.inputTokens;
|
|
@@ -938,9 +1040,11 @@ async function* agentLoop(messages, options) {
|
|
|
938
1040
|
if (response.usage.cacheWrite) {
|
|
939
1041
|
totalUsage.cacheWrite = (totalUsage.cacheWrite ?? 0) + response.usage.cacheWrite;
|
|
940
1042
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1043
|
+
if (!emptyExhausted) {
|
|
1044
|
+
messages.push(response.message);
|
|
1045
|
+
latestProviderUsage = response.usage;
|
|
1046
|
+
usageAnchorIndex = messages.length - 1;
|
|
1047
|
+
}
|
|
944
1048
|
const completedAt = Date.now();
|
|
945
1049
|
const outputTokensPerSecond = providerDurationMs > 0 && response.usage.outputTokens > 0 ? response.usage.outputTokens / (providerDurationMs / 1e3) : void 0;
|
|
946
1050
|
const timing = {
|
|
@@ -972,8 +1076,15 @@ async function* agentLoop(messages, options) {
|
|
|
972
1076
|
}
|
|
973
1077
|
consecutivePauses = 0;
|
|
974
1078
|
const allToolCalls = extractToolCalls(response.message.content);
|
|
975
|
-
if (response.stopReason !== "tool_use" && allToolCalls.length === 0) {
|
|
976
|
-
if (
|
|
1079
|
+
if (emptyExhausted || response.stopReason !== "tool_use" && allToolCalls.length === 0) {
|
|
1080
|
+
if (emptyExhausted) {
|
|
1081
|
+
diag("empty_response_exhausted", {
|
|
1082
|
+
provider: options.provider,
|
|
1083
|
+
model: options.model,
|
|
1084
|
+
stopReason: response.stopReason
|
|
1085
|
+
});
|
|
1086
|
+
yield { type: "truncated", reason: "empty_response", continued: false };
|
|
1087
|
+
} else if (response.stopReason === "max_tokens") {
|
|
977
1088
|
if (maxTokensContinuations < MAX_OUTPUT_CONTINUATIONS) {
|
|
978
1089
|
maxTokensContinuations++;
|
|
979
1090
|
diag("max_tokens_continuation", {
|
|
@@ -1195,6 +1306,7 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1195
1306
|
let resultContent;
|
|
1196
1307
|
let details;
|
|
1197
1308
|
let isError = false;
|
|
1309
|
+
let invalidArgAttempt;
|
|
1198
1310
|
const tool = options.toolMap.get(toolCall.name);
|
|
1199
1311
|
if (!tool) {
|
|
1200
1312
|
resultContent = `Unknown tool: ${toolCall.name}`;
|
|
@@ -1232,6 +1344,7 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1232
1344
|
const failureKey = `${toolCall.name}:${prettyError}`;
|
|
1233
1345
|
const failureCount = (options.invalidToolArgumentCounts.get(failureKey) ?? 0) + 1;
|
|
1234
1346
|
options.invalidToolArgumentCounts.set(failureKey, failureCount);
|
|
1347
|
+
invalidArgAttempt = failureCount;
|
|
1235
1348
|
resultContent = `Invalid arguments for tool \`${toolCall.name}\`:
|
|
1236
1349
|
` + prettyError + "\nRe-issue the call with each field as the correct type.";
|
|
1237
1350
|
if (failureCount >= 3) {
|
|
@@ -1248,6 +1361,8 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1248
1361
|
toolCall.name
|
|
1249
1362
|
);
|
|
1250
1363
|
}
|
|
1364
|
+
} else if (options.signal?.aborted && isAbortError(err)) {
|
|
1365
|
+
resultContent = indeterminateOutcomeText(toolCall.name);
|
|
1251
1366
|
} else {
|
|
1252
1367
|
resultContent = redactValue(err instanceof Error ? err.message : String(err));
|
|
1253
1368
|
}
|
|
@@ -1262,7 +1377,8 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
|
|
|
1262
1377
|
result: toolResultPreview(resultContent),
|
|
1263
1378
|
details,
|
|
1264
1379
|
isError,
|
|
1265
|
-
durationMs
|
|
1380
|
+
durationMs,
|
|
1381
|
+
...invalidArgAttempt === void 0 ? {} : { invalidArgAttempt }
|
|
1266
1382
|
});
|
|
1267
1383
|
return { toolCallId: toolCall.id, content: resultContent, isError };
|
|
1268
1384
|
}
|
|
@@ -1270,6 +1386,7 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1270
1386
|
const eventStream = new EventStream();
|
|
1271
1387
|
const state = { finalized: false };
|
|
1272
1388
|
const resultsById = /* @__PURE__ */ new Map();
|
|
1389
|
+
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1273
1390
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1274
1391
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1275
1392
|
const phases = [];
|
|
@@ -1294,6 +1411,7 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1294
1411
|
for (const phase of phases) {
|
|
1295
1412
|
if (options.signal?.aborted) break;
|
|
1296
1413
|
if (phase.sequential) {
|
|
1414
|
+
dispatchedIds.add(phase.sequential.id);
|
|
1297
1415
|
const record = await executeSingleToolCall(
|
|
1298
1416
|
phase.sequential,
|
|
1299
1417
|
options,
|
|
@@ -1301,6 +1419,7 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1301
1419
|
);
|
|
1302
1420
|
resultsById.set(record.toolCallId, record);
|
|
1303
1421
|
} else if (phase.parallel.length === 1) {
|
|
1422
|
+
dispatchedIds.add(phase.parallel[0].id);
|
|
1304
1423
|
const record = await executeSingleToolCall(
|
|
1305
1424
|
phase.parallel[0],
|
|
1306
1425
|
options,
|
|
@@ -1310,6 +1429,7 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1310
1429
|
} else {
|
|
1311
1430
|
await Promise.all(
|
|
1312
1431
|
phase.parallel.map(async (toolCall) => {
|
|
1432
|
+
dispatchedIds.add(toolCall.id);
|
|
1313
1433
|
const record = await executeSingleToolCall(
|
|
1314
1434
|
toolCall,
|
|
1315
1435
|
options,
|
|
@@ -1340,7 +1460,7 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
|
|
|
1340
1460
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1341
1461
|
state.finalized = true;
|
|
1342
1462
|
}
|
|
1343
|
-
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById);
|
|
1463
|
+
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1344
1464
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1345
1465
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1346
1466
|
return { toolResults, aborted };
|
|
@@ -1349,10 +1469,12 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1349
1469
|
const eventStream = new EventStream();
|
|
1350
1470
|
const state = { finalized: false };
|
|
1351
1471
|
const resultsById = /* @__PURE__ */ new Map();
|
|
1472
|
+
const dispatchedIds = /* @__PURE__ */ new Set();
|
|
1352
1473
|
const abortHandler = () => eventStream.abort(new Error("aborted"));
|
|
1353
1474
|
options.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1354
1475
|
Promise.all(
|
|
1355
1476
|
toolCalls.map(async (toolCall) => {
|
|
1477
|
+
dispatchedIds.add(toolCall.id);
|
|
1356
1478
|
const record = await executeSingleToolCall(
|
|
1357
1479
|
toolCall,
|
|
1358
1480
|
options,
|
|
@@ -1380,12 +1502,18 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
|
|
|
1380
1502
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
1381
1503
|
state.finalized = true;
|
|
1382
1504
|
}
|
|
1383
|
-
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById);
|
|
1505
|
+
const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds);
|
|
1384
1506
|
capToolResults(toolResults, options.maxToolResultChars);
|
|
1385
1507
|
capTurnToolResults(toolResults, options.maxTurnToolResultChars);
|
|
1386
1508
|
return { toolResults, aborted };
|
|
1387
1509
|
}
|
|
1388
|
-
function
|
|
1510
|
+
function cancelledBeforeStartText(name) {
|
|
1511
|
+
return `\`${name}\` was cancelled before it started, so it had no effect. Safe to retry.`;
|
|
1512
|
+
}
|
|
1513
|
+
function indeterminateOutcomeText(name) {
|
|
1514
|
+
return `\`${name}\` started running and was cut off before it reported back, so its outcome is UNKNOWN \u2014 it may have completed. Check the real state (re-read the file, re-run a status command) before retrying it, and do not tell the user it did not happen.`;
|
|
1515
|
+
}
|
|
1516
|
+
function buildToolResults(initialToolResults, toolCalls, resultsById, dispatchedIds) {
|
|
1389
1517
|
const toolResults = [...initialToolResults];
|
|
1390
1518
|
for (const toolCall of toolCalls) {
|
|
1391
1519
|
const result = resultsById.get(toolCall.id);
|
|
@@ -1397,10 +1525,11 @@ function buildToolResults(initialToolResults, toolCalls, resultsById) {
|
|
|
1397
1525
|
isError: result.isError || void 0
|
|
1398
1526
|
});
|
|
1399
1527
|
} else {
|
|
1528
|
+
const dispatched = dispatchedIds?.has(toolCall.id) ?? true;
|
|
1400
1529
|
toolResults.push({
|
|
1401
1530
|
type: "tool_result",
|
|
1402
1531
|
toolCallId: toolCall.id,
|
|
1403
|
-
content:
|
|
1532
|
+
content: dispatched ? indeterminateOutcomeText(toolCall.name) : cancelledBeforeStartText(toolCall.name),
|
|
1404
1533
|
isError: true
|
|
1405
1534
|
});
|
|
1406
1535
|
}
|
|
@@ -1548,32 +1677,22 @@ function repairToolPairingAdjacent(messages) {
|
|
|
1548
1677
|
const msg = messages[i];
|
|
1549
1678
|
if (msg.role !== "assistant") continue;
|
|
1550
1679
|
if (typeof msg.content === "string" || !Array.isArray(msg.content)) continue;
|
|
1551
|
-
const
|
|
1552
|
-
if (
|
|
1680
|
+
const orphanCalls = msg.content.filter((p) => p.type === "tool_call").map((p) => p);
|
|
1681
|
+
if (orphanCalls.length === 0) continue;
|
|
1682
|
+
const repaired = (call) => ({
|
|
1683
|
+
type: "tool_result",
|
|
1684
|
+
toolCallId: call.id,
|
|
1685
|
+
content: indeterminateOutcomeText(call.name),
|
|
1686
|
+
isError: true
|
|
1687
|
+
});
|
|
1553
1688
|
const next = messages[i + 1];
|
|
1554
1689
|
if (next?.role === "tool" && Array.isArray(next.content)) {
|
|
1555
1690
|
const existingIds = new Set(next.content.map((r) => r.toolCallId));
|
|
1556
|
-
const
|
|
1557
|
-
|
|
1558
|
-
for (const id of missing) {
|
|
1559
|
-
next.content.push({
|
|
1560
|
-
type: "tool_result",
|
|
1561
|
-
toolCallId: id,
|
|
1562
|
-
content: "Tool execution was interrupted.",
|
|
1563
|
-
isError: true
|
|
1564
|
-
});
|
|
1565
|
-
}
|
|
1691
|
+
for (const call of orphanCalls) {
|
|
1692
|
+
if (!existingIds.has(call.id)) next.content.push(repaired(call));
|
|
1566
1693
|
}
|
|
1567
1694
|
} else {
|
|
1568
|
-
messages.splice(i + 1, 0, {
|
|
1569
|
-
role: "tool",
|
|
1570
|
-
content: toolCallIds.map((id) => ({
|
|
1571
|
-
type: "tool_result",
|
|
1572
|
-
toolCallId: id,
|
|
1573
|
-
content: "Tool execution was interrupted.",
|
|
1574
|
-
isError: true
|
|
1575
|
-
}))
|
|
1576
|
-
});
|
|
1695
|
+
messages.splice(i + 1, 0, { role: "tool", content: orphanCalls.map(repaired) });
|
|
1577
1696
|
}
|
|
1578
1697
|
}
|
|
1579
1698
|
const toolCallIdSet = /* @__PURE__ */ new Set();
|
|
@@ -1732,6 +1851,8 @@ export {
|
|
|
1732
1851
|
Agent,
|
|
1733
1852
|
AgentStream,
|
|
1734
1853
|
agentLoop,
|
|
1854
|
+
cancelledBeforeStartText,
|
|
1855
|
+
indeterminateOutcomeText,
|
|
1735
1856
|
isAbortError,
|
|
1736
1857
|
isBillingError,
|
|
1737
1858
|
isContextOverflow,
|