@prestyj/agent 5.8.0 → 5.10.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 +123 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +122 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -87,9 +87,22 @@ interface AgentMaxTurnsEvent {
|
|
|
87
87
|
totalTurns: number;
|
|
88
88
|
maxTurns: number;
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Warning signal emitted when a turn ended on a non-clean stop reason —
|
|
92
|
+
* `max_tokens` (output clipped at the model's output-token limit), `refusal`,
|
|
93
|
+
* or a provider-reported `error` stop. Distinguishes a truncated/degraded
|
|
94
|
+
* completion from a clean one so hosts can warn the user instead of silently
|
|
95
|
+
* presenting incomplete output as done.
|
|
96
|
+
*/
|
|
97
|
+
interface AgentTruncatedEvent {
|
|
98
|
+
type: "truncated";
|
|
99
|
+
reason: "max_tokens" | "refusal" | "provider_error";
|
|
100
|
+
/** True when the loop injected a continuation and will keep going. */
|
|
101
|
+
continued: boolean;
|
|
102
|
+
}
|
|
90
103
|
interface AgentRetryEvent {
|
|
91
104
|
type: "retry";
|
|
92
|
-
reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact" | "tool_argument_glitch";
|
|
105
|
+
reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact" | "tool_argument_glitch" | "runaway_toolcall";
|
|
93
106
|
attempt: number;
|
|
94
107
|
maxAttempts: number;
|
|
95
108
|
delayMs: number;
|
|
@@ -135,7 +148,7 @@ interface AgentFollowUpMessageEvent {
|
|
|
135
148
|
type: "follow_up_message";
|
|
136
149
|
content: Message["content"];
|
|
137
150
|
}
|
|
138
|
-
type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentErrorEvent;
|
|
151
|
+
type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTruncatedEvent | AgentErrorEvent;
|
|
139
152
|
interface TransformContextOptions {
|
|
140
153
|
/** Force a transform after the provider reports context overflow. */
|
|
141
154
|
force?: boolean;
|
|
@@ -311,4 +324,13 @@ declare function isBillingError(err: unknown): boolean;
|
|
|
311
324
|
declare function isUsageLimitError(err: unknown): boolean;
|
|
312
325
|
declare function agentLoop(messages: Message[], options: AgentOptions): AsyncGenerator<AgentEvent, AgentResult>;
|
|
313
326
|
|
|
314
|
-
|
|
327
|
+
/**
|
|
328
|
+
* A local inference server (llama.cpp, vLLM, Ollama, LM Studio) can spend
|
|
329
|
+
* minutes prefilling a large prompt before it emits its first token. The
|
|
330
|
+
* first-event watchdog that protects hosted streams turns that into an abort
|
|
331
|
+
* → retry → cold-prefill loop that never converges, so it is disabled for
|
|
332
|
+
* loopback backends.
|
|
333
|
+
*/
|
|
334
|
+
declare function isLocalBackendUrl(baseUrl?: string): boolean;
|
|
335
|
+
|
|
336
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -87,9 +87,22 @@ interface AgentMaxTurnsEvent {
|
|
|
87
87
|
totalTurns: number;
|
|
88
88
|
maxTurns: number;
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Warning signal emitted when a turn ended on a non-clean stop reason —
|
|
92
|
+
* `max_tokens` (output clipped at the model's output-token limit), `refusal`,
|
|
93
|
+
* or a provider-reported `error` stop. Distinguishes a truncated/degraded
|
|
94
|
+
* completion from a clean one so hosts can warn the user instead of silently
|
|
95
|
+
* presenting incomplete output as done.
|
|
96
|
+
*/
|
|
97
|
+
interface AgentTruncatedEvent {
|
|
98
|
+
type: "truncated";
|
|
99
|
+
reason: "max_tokens" | "refusal" | "provider_error";
|
|
100
|
+
/** True when the loop injected a continuation and will keep going. */
|
|
101
|
+
continued: boolean;
|
|
102
|
+
}
|
|
90
103
|
interface AgentRetryEvent {
|
|
91
104
|
type: "retry";
|
|
92
|
-
reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact" | "tool_argument_glitch";
|
|
105
|
+
reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact" | "tool_argument_glitch" | "runaway_toolcall";
|
|
93
106
|
attempt: number;
|
|
94
107
|
maxAttempts: number;
|
|
95
108
|
delayMs: number;
|
|
@@ -135,7 +148,7 @@ interface AgentFollowUpMessageEvent {
|
|
|
135
148
|
type: "follow_up_message";
|
|
136
149
|
content: Message["content"];
|
|
137
150
|
}
|
|
138
|
-
type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentErrorEvent;
|
|
151
|
+
type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTruncatedEvent | AgentErrorEvent;
|
|
139
152
|
interface TransformContextOptions {
|
|
140
153
|
/** Force a transform after the provider reports context overflow. */
|
|
141
154
|
force?: boolean;
|
|
@@ -311,4 +324,13 @@ declare function isBillingError(err: unknown): boolean;
|
|
|
311
324
|
declare function isUsageLimitError(err: unknown): boolean;
|
|
312
325
|
declare function agentLoop(messages: Message[], options: AgentOptions): AsyncGenerator<AgentEvent, AgentResult>;
|
|
313
326
|
|
|
314
|
-
|
|
327
|
+
/**
|
|
328
|
+
* A local inference server (llama.cpp, vLLM, Ollama, LM Studio) can spend
|
|
329
|
+
* minutes prefilling a large prompt before it emits its first token. The
|
|
330
|
+
* first-event watchdog that protects hosted streams turns that into an abort
|
|
331
|
+
* → retry → cold-prefill loop that never converges, so it is disabled for
|
|
332
|
+
* loopback backends.
|
|
333
|
+
*/
|
|
334
|
+
declare function isLocalBackendUrl(baseUrl?: string): boolean;
|
|
335
|
+
|
|
336
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,30 @@ import {
|
|
|
10
10
|
isHardBillingMessage,
|
|
11
11
|
redactValue
|
|
12
12
|
} from "@prestyj/ai";
|
|
13
|
+
|
|
14
|
+
// src/local-backend.ts
|
|
15
|
+
function isLocalBackendUrl(baseUrl) {
|
|
16
|
+
if (!baseUrl) return false;
|
|
17
|
+
let host;
|
|
18
|
+
try {
|
|
19
|
+
host = new URL(baseUrl).hostname.toLowerCase();
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (host === "[::1]" || host === "::1") return true;
|
|
24
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
25
|
+
if (host === "0.0.0.0") return true;
|
|
26
|
+
if (host.endsWith(".local")) return true;
|
|
27
|
+
const ipv4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
28
|
+
if (ipv4) {
|
|
29
|
+
const octets = ipv4.slice(1).map(Number);
|
|
30
|
+
if (octets.some((n) => n > 255)) return false;
|
|
31
|
+
return octets[0] === 127;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/agent-loop.ts
|
|
13
37
|
var DEFAULT_MAX_TURNS = 300;
|
|
14
38
|
var _diagFn = null;
|
|
15
39
|
function setStreamDiagnostic(fn) {
|
|
@@ -188,7 +212,11 @@ function createAbortError() {
|
|
|
188
212
|
}
|
|
189
213
|
function abortablePromise(promise, signal) {
|
|
190
214
|
if (!signal) return promise;
|
|
191
|
-
if (signal.aborted)
|
|
215
|
+
if (signal.aborted) {
|
|
216
|
+
promise.catch(() => {
|
|
217
|
+
});
|
|
218
|
+
return Promise.reject(createAbortError());
|
|
219
|
+
}
|
|
192
220
|
return new Promise((resolve, reject) => {
|
|
193
221
|
let settled = false;
|
|
194
222
|
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
@@ -244,6 +272,7 @@ async function* agentLoop(messages, options) {
|
|
|
244
272
|
let overloadRetries = 0;
|
|
245
273
|
let emptyResponseRetries = 0;
|
|
246
274
|
let stallRetries = 0;
|
|
275
|
+
let runawayToolcallRetries = 0;
|
|
247
276
|
let overflowCompactionAttempts = 0;
|
|
248
277
|
let toolResultTruncationAttempted = false;
|
|
249
278
|
const invalidToolArgumentCounts = /* @__PURE__ */ new Map();
|
|
@@ -252,11 +281,16 @@ async function* agentLoop(messages, options) {
|
|
|
252
281
|
const MAX_OVERLOAD_RETRIES = 10;
|
|
253
282
|
const MAX_EMPTY_RESPONSE_RETRIES = 2;
|
|
254
283
|
const MAX_STALL_RETRIES = 10;
|
|
284
|
+
const MAX_RUNAWAY_TOOLCALL_RETRIES = 2;
|
|
285
|
+
const RUNAWAY_TOOLCALL_RETRY_DELAY_MS = 1e3;
|
|
255
286
|
const MAX_OVERFLOW_COMPACTIONS = 2;
|
|
256
287
|
const STALL_RETRIES_BEFORE_NON_STREAMING = 2;
|
|
257
288
|
const STALL_DELAY_MS = 1e3;
|
|
258
289
|
const MIN_PARTIAL_PRESERVE_CHARS = 200;
|
|
259
290
|
const PARTIAL_CONTINUATION_PROMPT = "[Your previous response was cut off by a connection failure. The text above is what was already delivered to the user. Continue exactly from where it stopped \u2014 do not repeat or restart it.]";
|
|
291
|
+
const MAX_OUTPUT_CONTINUATIONS = 2;
|
|
292
|
+
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.]";
|
|
293
|
+
let maxTokensContinuations = 0;
|
|
260
294
|
const OVERLOAD_BASE_DELAY_MS = 2e3;
|
|
261
295
|
const OVERLOAD_MAX_DELAY_MS = 3e4;
|
|
262
296
|
const STREAM_FIRST_EVENT_TIMEOUT_MS = 45e3;
|
|
@@ -266,9 +300,10 @@ async function* agentLoop(messages, options) {
|
|
|
266
300
|
const STREAM_THINKING_IDLE_TIMEOUT_MS = 3e5;
|
|
267
301
|
const STREAM_THINKING_HARD_TIMEOUT_MS = 6e5;
|
|
268
302
|
const NON_STREAMING_HARD_TIMEOUT_MS = 3e5;
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
const
|
|
303
|
+
const usesSilentReasoningBudget = options.provider === "sakana" || options.provider === "openai" && options.thinking != null;
|
|
304
|
+
const localBackend = isLocalBackendUrl(options.baseUrl);
|
|
305
|
+
const firstEventTimeoutMs = localBackend ? Number.POSITIVE_INFINITY : usesSilentReasoningBudget ? STREAM_THINKING_IDLE_TIMEOUT_MS : STREAM_FIRST_EVENT_TIMEOUT_MS;
|
|
306
|
+
const initialHardTimeoutMs = localBackend || usesSilentReasoningBudget ? STREAM_THINKING_HARD_TIMEOUT_MS : STREAM_HARD_TIMEOUT_MS;
|
|
272
307
|
const MAX_TOOLCALL_DELTA_CHARS = 1e6;
|
|
273
308
|
const MAX_TOOLCALL_DELTA_EVENTS = 2e4;
|
|
274
309
|
let logicalTurnStartedAt = 0;
|
|
@@ -296,7 +331,11 @@ async function* agentLoop(messages, options) {
|
|
|
296
331
|
messages: messages.length,
|
|
297
332
|
chars: msgChars,
|
|
298
333
|
provider: options.provider,
|
|
299
|
-
model: options.model
|
|
334
|
+
model: options.model,
|
|
335
|
+
thinking: options.thinking ?? "off",
|
|
336
|
+
firstEventTimeoutMs,
|
|
337
|
+
initialHardTimeoutMs,
|
|
338
|
+
localBackend
|
|
300
339
|
});
|
|
301
340
|
}
|
|
302
341
|
if (firstTurn && options.getSteeringMessages) {
|
|
@@ -354,6 +393,7 @@ async function* agentLoop(messages, options) {
|
|
|
354
393
|
if (useNonStreamingFallback) return;
|
|
355
394
|
if (idleTimer) clearTimeout(idleTimer);
|
|
356
395
|
const timeoutMs = hasReceivedEvent ? STREAM_IDLE_TIMEOUT_MS : hasReceivedThinking ? STREAM_THINKING_IDLE_TIMEOUT_MS : firstEventTimeoutMs;
|
|
396
|
+
if (!Number.isFinite(timeoutMs)) return;
|
|
357
397
|
idleTimer = setTimeout(() => {
|
|
358
398
|
diag("idle_timeout_fired", {
|
|
359
399
|
events: streamEventCount,
|
|
@@ -659,11 +699,33 @@ async function* agentLoop(messages, options) {
|
|
|
659
699
|
provider: options.provider,
|
|
660
700
|
model: options.model
|
|
661
701
|
});
|
|
702
|
+
if (runawayToolcallRetries < MAX_RUNAWAY_TOOLCALL_RETRIES) {
|
|
703
|
+
runawayToolcallRetries++;
|
|
704
|
+
const delayMs = RUNAWAY_TOOLCALL_RETRY_DELAY_MS * runawayToolcallRetries;
|
|
705
|
+
diag("retry", {
|
|
706
|
+
reason: "runaway_toolcall",
|
|
707
|
+
attempt: runawayToolcallRetries,
|
|
708
|
+
maxAttempts: MAX_RUNAWAY_TOOLCALL_RETRIES,
|
|
709
|
+
delayMs,
|
|
710
|
+
...runawayDetected
|
|
711
|
+
});
|
|
712
|
+
yield {
|
|
713
|
+
type: "retry",
|
|
714
|
+
reason: "runaway_toolcall",
|
|
715
|
+
attempt: runawayToolcallRetries,
|
|
716
|
+
maxAttempts: MAX_RUNAWAY_TOOLCALL_RETRIES,
|
|
717
|
+
delayMs,
|
|
718
|
+
silent: true
|
|
719
|
+
};
|
|
720
|
+
await abortableSleep(delayMs, options.signal);
|
|
721
|
+
turn--;
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
662
724
|
const detail = runawayDetected.kind === "chars" ? `${(runawayDetected.chars / 1024).toFixed(0)} KB of tool-call arguments` : `${runawayDetected.events} tool-call delta events`;
|
|
663
725
|
yield {
|
|
664
726
|
type: "error",
|
|
665
727
|
error: new Error(
|
|
666
|
-
`The model
|
|
728
|
+
`The model repeatedly failed to close a tool call after ${MAX_RUNAWAY_TOOLCALL_RETRIES} automatic retries (${detail}). Switch models and retry; your conversation is preserved.`
|
|
667
729
|
)
|
|
668
730
|
};
|
|
669
731
|
break;
|
|
@@ -716,15 +778,29 @@ async function* agentLoop(messages, options) {
|
|
|
716
778
|
continue;
|
|
717
779
|
}
|
|
718
780
|
if (transportFailure) {
|
|
781
|
+
const cause = malformed ? "malformed_stream" : socketDrop ? "socket_drop" : "stream_stall";
|
|
719
782
|
diag("stall_exhausted", {
|
|
720
783
|
stallRetries: MAX_STALL_RETRIES,
|
|
721
784
|
provider: options.provider,
|
|
722
|
-
model: options.model
|
|
785
|
+
model: options.model,
|
|
786
|
+
cause,
|
|
787
|
+
nonStreaming: useNonStreamingFallback,
|
|
788
|
+
events: streamEventCount,
|
|
789
|
+
eventTypes: eventTypeCounts,
|
|
790
|
+
lastEventType,
|
|
791
|
+
sinceLastEventMs: Date.now() - lastEventTime,
|
|
792
|
+
attemptDurationMs: Date.now() - streamCallStart,
|
|
793
|
+
maxConsumerLagMs
|
|
723
794
|
});
|
|
724
795
|
yield {
|
|
725
796
|
type: "error",
|
|
726
|
-
error: new
|
|
727
|
-
`The API provider
|
|
797
|
+
error: new EZCoderAIError(
|
|
798
|
+
`The connection to the API provider stopped responding after ${MAX_STALL_RETRIES} automatic retries. Your conversation is preserved.`,
|
|
799
|
+
{
|
|
800
|
+
source: "network",
|
|
801
|
+
hint: "Retry once. If it keeps happening on this device, disable any VPN or proxy and allow EZ Coder through firewall or antivirus web protection.",
|
|
802
|
+
cause: err
|
|
803
|
+
}
|
|
728
804
|
)
|
|
729
805
|
};
|
|
730
806
|
break;
|
|
@@ -764,6 +840,7 @@ async function* agentLoop(messages, options) {
|
|
|
764
840
|
}
|
|
765
841
|
overloadRetries = 0;
|
|
766
842
|
stallRetries = 0;
|
|
843
|
+
runawayToolcallRetries = 0;
|
|
767
844
|
const contentArr = Array.isArray(response.message.content) ? response.message.content : null;
|
|
768
845
|
const hasActionableContent = response.message.content !== "" && contentArr !== null && contentArr.some(
|
|
769
846
|
(p) => p.type === "text" || p.type === "tool_call" || p.type === "server_tool_call"
|
|
@@ -835,6 +912,27 @@ async function* agentLoop(messages, options) {
|
|
|
835
912
|
consecutivePauses = 0;
|
|
836
913
|
const allToolCalls = extractToolCalls(response.message.content);
|
|
837
914
|
if (response.stopReason !== "tool_use" && allToolCalls.length === 0) {
|
|
915
|
+
if (response.stopReason === "max_tokens") {
|
|
916
|
+
if (maxTokensContinuations < MAX_OUTPUT_CONTINUATIONS) {
|
|
917
|
+
maxTokensContinuations++;
|
|
918
|
+
diag("max_tokens_continuation", {
|
|
919
|
+
attempt: maxTokensContinuations,
|
|
920
|
+
maxAttempts: MAX_OUTPUT_CONTINUATIONS,
|
|
921
|
+
provider: options.provider,
|
|
922
|
+
model: options.model
|
|
923
|
+
});
|
|
924
|
+
yield { type: "truncated", reason: "max_tokens", continued: true };
|
|
925
|
+
messages.push({ role: "user", content: MAX_TOKENS_CONTINUATION_PROMPT });
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
yield { type: "truncated", reason: "max_tokens", continued: false };
|
|
929
|
+
} else if (response.stopReason === "refusal" || response.stopReason === "error") {
|
|
930
|
+
yield {
|
|
931
|
+
type: "truncated",
|
|
932
|
+
reason: response.stopReason === "refusal" ? "refusal" : "provider_error",
|
|
933
|
+
continued: false
|
|
934
|
+
};
|
|
935
|
+
}
|
|
838
936
|
if (options.getSteeringMessages) {
|
|
839
937
|
const steering = await options.getSteeringMessages();
|
|
840
938
|
if (steering && steering.length > 0) {
|
|
@@ -1201,16 +1299,22 @@ function capToolResults(toolResults, maxToolResultChars) {
|
|
|
1201
1299
|
const max = Math.min(maxToolResultChars, hardMax);
|
|
1202
1300
|
for (const toolResult of toolResults) {
|
|
1203
1301
|
if (typeof toolResult.content !== "string" || toolResult.content.length <= max) continue;
|
|
1302
|
+
const originalChars = toolResult.content.length;
|
|
1204
1303
|
const headChars = Math.floor(max * 0.7);
|
|
1205
1304
|
const tailChars = max - headChars;
|
|
1206
1305
|
const head = toolResult.content.slice(0, headChars);
|
|
1207
1306
|
const tail = toolResult.content.slice(-tailChars);
|
|
1208
|
-
const omitted =
|
|
1307
|
+
const omitted = originalChars - headChars - tailChars;
|
|
1209
1308
|
toolResult.content = head + `
|
|
1210
1309
|
|
|
1211
1310
|
[... ${omitted} characters omitted ...]
|
|
1212
1311
|
|
|
1213
1312
|
` + tail;
|
|
1313
|
+
toolResult.capped = {
|
|
1314
|
+
originalChars,
|
|
1315
|
+
keptChars: toolResult.content.length,
|
|
1316
|
+
scope: "per-result"
|
|
1317
|
+
};
|
|
1214
1318
|
}
|
|
1215
1319
|
}
|
|
1216
1320
|
function capTurnToolResults(toolResults, maxTurnToolResultChars) {
|
|
@@ -1231,14 +1335,20 @@ function capTurnToolResults(toolResults, maxTurnToolResultChars) {
|
|
|
1231
1335
|
continue;
|
|
1232
1336
|
}
|
|
1233
1337
|
remaining -= fairShare;
|
|
1338
|
+
const originalChars = toolResult.content.length;
|
|
1234
1339
|
const headChars = Math.floor(fairShare * 0.7);
|
|
1235
1340
|
const tailChars = fairShare - headChars;
|
|
1236
|
-
const omitted =
|
|
1341
|
+
const omitted = originalChars - fairShare;
|
|
1237
1342
|
toolResult.content = toolResult.content.slice(0, headChars) + `
|
|
1238
1343
|
|
|
1239
1344
|
[... ${omitted} characters trimmed: this turn's combined tool results exceeded the per-turn budget. Re-run this call alone with narrower filters or offset/limit if you need the omitted content ...]
|
|
1240
1345
|
|
|
1241
1346
|
` + (tailChars > 0 ? toolResult.content.slice(-tailChars) : "");
|
|
1347
|
+
toolResult.capped = {
|
|
1348
|
+
originalChars: toolResult.capped?.originalChars ?? originalChars,
|
|
1349
|
+
keptChars: toolResult.content.length,
|
|
1350
|
+
scope: "per-turn"
|
|
1351
|
+
};
|
|
1242
1352
|
}
|
|
1243
1353
|
}
|
|
1244
1354
|
function normalizeToolResult(raw) {
|
|
@@ -1509,6 +1619,7 @@ export {
|
|
|
1509
1619
|
isAbortError,
|
|
1510
1620
|
isBillingError,
|
|
1511
1621
|
isContextOverflow,
|
|
1622
|
+
isLocalBackendUrl,
|
|
1512
1623
|
isUsageLimitError,
|
|
1513
1624
|
setStreamDiagnostic
|
|
1514
1625
|
};
|