@prestyj/agent 5.7.0 → 5.8.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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { StreamOptions, Message, Tool, ToolResultContent, ServerToolDefinition, StopReason, Usage, AssistantMessage } from '@prestyj/ai';
1
+ import { StreamOptions, Message, Tool, ToolResultContent, ServerToolDefinition, Usage, StopReason, AssistantMessage } from '@prestyj/ai';
2
2
  import { z } from 'zod';
3
3
 
4
4
  interface StructuredToolResult {
@@ -136,6 +136,14 @@ interface AgentFollowUpMessageEvent {
136
136
  content: Message["content"];
137
137
  }
138
138
  type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentErrorEvent;
139
+ interface TransformContextOptions {
140
+ /** Force a transform after the provider reports context overflow. */
141
+ force?: boolean;
142
+ /** Latest successful provider usage, anchored at its assistant message. */
143
+ usage?: Usage;
144
+ /** Messages appended after that usage sample and not yet seen by the provider. */
145
+ pendingMessages: Message[];
146
+ }
139
147
  interface AgentOptions {
140
148
  provider: StreamOptions["provider"];
141
149
  model: string;
@@ -180,6 +188,10 @@ interface AgentOptions {
180
188
  clearToolUses?: boolean;
181
189
  /** Max characters for a single tool result. Results exceeding this are truncated with a notice. */
182
190
  maxToolResultChars?: number;
191
+ /** Aggregate budget for ALL tool results in one assistant turn. Protects
192
+ * against parallel fan-outs injecting huge uncached context in one turn;
193
+ * the largest results are trimmed (water-filling) with a re-run notice. */
194
+ maxTurnToolResultChars?: number;
183
195
  /** Max consecutive pause_turn continuations before stopping (default: 5).
184
196
  * Prevents infinite loops when server-side tools keep pausing. */
185
197
  maxContinuations?: number;
@@ -188,12 +200,12 @@ interface AgentOptions {
188
200
  * the messages array (e.g. compaction, truncation). Return the same array
189
201
  * for no-op, or a new array to replace the conversation context.
190
202
  *
203
+ * The latest provider usage is authoritative for the history through its
204
+ * assistant response. `pendingMessages` contains context appended afterward.
191
205
  * When `options.force` is true, the caller should compact unconditionally
192
206
  * (e.g. after a context overflow error from the API).
193
207
  */
194
- transformContext?: (messages: Message[], options?: {
195
- force?: boolean;
196
- }) => Message[] | Promise<Message[]>;
208
+ transformContext?: (messages: Message[], options: TransformContextOptions) => Message[] | Promise<Message[]>;
197
209
  /**
198
210
  * Polled after tool execution completes each turn. Returns user messages
199
211
  * to inject into the conversation before the next LLM call (steering).
@@ -299,4 +311,4 @@ declare function isBillingError(err: unknown): boolean;
299
311
  declare function isUsageLimitError(err: unknown): boolean;
300
312
  declare function agentLoop(messages: Message[], options: AgentOptions): AsyncGenerator<AgentEvent, AgentResult>;
301
313
 
302
- 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, agentLoop, isAbortError, isBillingError, isContextOverflow, isUsageLimitError, setStreamDiagnostic };
314
+ 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, isUsageLimitError, setStreamDiagnostic };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { StreamOptions, Message, Tool, ToolResultContent, ServerToolDefinition, StopReason, Usage, AssistantMessage } from '@prestyj/ai';
1
+ import { StreamOptions, Message, Tool, ToolResultContent, ServerToolDefinition, Usage, StopReason, AssistantMessage } from '@prestyj/ai';
2
2
  import { z } from 'zod';
3
3
 
4
4
  interface StructuredToolResult {
@@ -136,6 +136,14 @@ interface AgentFollowUpMessageEvent {
136
136
  content: Message["content"];
137
137
  }
138
138
  type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentErrorEvent;
139
+ interface TransformContextOptions {
140
+ /** Force a transform after the provider reports context overflow. */
141
+ force?: boolean;
142
+ /** Latest successful provider usage, anchored at its assistant message. */
143
+ usage?: Usage;
144
+ /** Messages appended after that usage sample and not yet seen by the provider. */
145
+ pendingMessages: Message[];
146
+ }
139
147
  interface AgentOptions {
140
148
  provider: StreamOptions["provider"];
141
149
  model: string;
@@ -180,6 +188,10 @@ interface AgentOptions {
180
188
  clearToolUses?: boolean;
181
189
  /** Max characters for a single tool result. Results exceeding this are truncated with a notice. */
182
190
  maxToolResultChars?: number;
191
+ /** Aggregate budget for ALL tool results in one assistant turn. Protects
192
+ * against parallel fan-outs injecting huge uncached context in one turn;
193
+ * the largest results are trimmed (water-filling) with a re-run notice. */
194
+ maxTurnToolResultChars?: number;
183
195
  /** Max consecutive pause_turn continuations before stopping (default: 5).
184
196
  * Prevents infinite loops when server-side tools keep pausing. */
185
197
  maxContinuations?: number;
@@ -188,12 +200,12 @@ interface AgentOptions {
188
200
  * the messages array (e.g. compaction, truncation). Return the same array
189
201
  * for no-op, or a new array to replace the conversation context.
190
202
  *
203
+ * The latest provider usage is authoritative for the history through its
204
+ * assistant response. `pendingMessages` contains context appended afterward.
191
205
  * When `options.force` is true, the caller should compact unconditionally
192
206
  * (e.g. after a context overflow error from the API).
193
207
  */
194
- transformContext?: (messages: Message[], options?: {
195
- force?: boolean;
196
- }) => Message[] | Promise<Message[]>;
208
+ transformContext?: (messages: Message[], options: TransformContextOptions) => Message[] | Promise<Message[]>;
197
209
  /**
198
210
  * Polled after tool execution completes each turn. Returns user messages
199
211
  * to inject into the conversation before the next LLM call (steering).
@@ -299,4 +311,4 @@ declare function isBillingError(err: unknown): boolean;
299
311
  declare function isUsageLimitError(err: unknown): boolean;
300
312
  declare function agentLoop(messages: Message[], options: AgentOptions): AsyncGenerator<AgentEvent, AgentResult>;
301
313
 
302
- 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, agentLoop, isAbortError, isBillingError, isContextOverflow, isUsageLimitError, setStreamDiagnostic };
314
+ 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, isUsageLimitError, setStreamDiagnostic };
package/dist/index.js CHANGED
@@ -127,7 +127,7 @@ function classifyOverload(err) {
127
127
  if (statusCode === 529 || msg.includes("overloaded") || msg.includes("529")) {
128
128
  return "overloaded";
129
129
  }
130
- if (statusCode === 500 || statusCode === 502 || statusCode === 503 || statusCode === 504 || msg.includes("api_error") || msg.includes("server_error") || msg.includes("internal server error") || msg.includes("bad gateway") || msg.includes("service unavailable") || msg.includes("gateway timeout")) {
130
+ if (statusCode === 500 || statusCode === 502 || statusCode === 503 || statusCode === 504 || statusCode === 507 || msg.includes("api_error") || msg.includes("server_error") || msg.includes("internal server error") || msg.includes("bad gateway") || msg.includes("service unavailable") || msg.includes("gateway timeout") || msg.includes("exceeded request buffer limit while retrying upstream")) {
131
131
  return "provider_error";
132
132
  }
133
133
  if (isOpaqueProviderMessage(err.message)) {
@@ -233,6 +233,8 @@ async function* agentLoop(messages, options) {
233
233
  const maxContinuations = options.maxContinuations ?? 5;
234
234
  let toolMap = new Map((options.tools ?? []).map((t) => [t.name, t]));
235
235
  const totalUsage = { inputTokens: 0, outputTokens: 0 };
236
+ let latestProviderUsage;
237
+ let usageAnchorIndex;
236
238
  let turn = 0;
237
239
  let hitMaxTurns = false;
238
240
  let firstTurn = true;
@@ -309,7 +311,11 @@ async function* agentLoop(messages, options) {
309
311
  firstTurn = false;
310
312
  if (options.transformContext) {
311
313
  diag("transform_start");
312
- const transformed = await options.transformContext(messages);
314
+ const pendingMessages = usageAnchorIndex === void 0 ? [] : messages.slice(usageAnchorIndex + 1);
315
+ const transformed = await options.transformContext(messages, {
316
+ usage: latestProviderUsage,
317
+ pendingMessages
318
+ });
313
319
  if (transformed !== messages) {
314
320
  diag("transform_compacted", {
315
321
  before: messages.length,
@@ -317,6 +323,8 @@ async function* agentLoop(messages, options) {
317
323
  });
318
324
  messages.length = 0;
319
325
  messages.push(...transformed);
326
+ latestProviderUsage = void 0;
327
+ usageAnchorIndex = void 0;
320
328
  }
321
329
  diag("transform_end");
322
330
  }
@@ -577,10 +585,17 @@ async function* agentLoop(messages, options) {
577
585
  ...overflowDetails
578
586
  });
579
587
  try {
580
- const compacted = await options.transformContext(messages, { force: true });
588
+ const pendingMessages = usageAnchorIndex === void 0 ? [] : messages.slice(usageAnchorIndex + 1);
589
+ const compacted = await options.transformContext(messages, {
590
+ force: true,
591
+ usage: latestProviderUsage,
592
+ pendingMessages
593
+ });
581
594
  if (compacted !== messages && compacted.length < messages.length) {
582
595
  messages.length = 0;
583
596
  messages.push(...compacted);
597
+ latestProviderUsage = void 0;
598
+ usageAnchorIndex = void 0;
584
599
  diag("overflow_compact_success", {
585
600
  attempt: overflowCompactionAttempts,
586
601
  messages: messages.length,
@@ -786,6 +801,8 @@ async function* agentLoop(messages, options) {
786
801
  totalUsage.cacheWrite = (totalUsage.cacheWrite ?? 0) + response.usage.cacheWrite;
787
802
  }
788
803
  messages.push(response.message);
804
+ latestProviderUsage = response.usage;
805
+ usageAnchorIndex = messages.length - 1;
789
806
  const completedAt = Date.now();
790
807
  const outputTokensPerSecond = providerDurationMs > 0 && response.usage.outputTokens > 0 ? response.usage.outputTokens / (providerDurationMs / 1e3) : void 0;
791
808
  const timing = {
@@ -873,6 +890,7 @@ async function* agentLoop(messages, options) {
873
890
  const executionOptions = {
874
891
  signal: options.signal,
875
892
  maxToolResultChars: options.maxToolResultChars,
893
+ maxTurnToolResultChars: options.maxTurnToolResultChars,
876
894
  toolMap,
877
895
  invalidToolArgumentCounts,
878
896
  markFatalToolArgumentError
@@ -1112,6 +1130,7 @@ async function* executeToolCallsMixed(toolCalls, initialToolResults, options) {
1112
1130
  }
1113
1131
  const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById);
1114
1132
  capToolResults(toolResults, options.maxToolResultChars);
1133
+ capTurnToolResults(toolResults, options.maxTurnToolResultChars);
1115
1134
  return { toolResults, aborted };
1116
1135
  }
1117
1136
  async function* executeToolCallsParallel(toolCalls, initialToolResults, options) {
@@ -1151,6 +1170,7 @@ async function* executeToolCallsParallel(toolCalls, initialToolResults, options)
1151
1170
  }
1152
1171
  const toolResults = buildToolResults(initialToolResults, toolCalls, resultsById);
1153
1172
  capToolResults(toolResults, options.maxToolResultChars);
1173
+ capTurnToolResults(toolResults, options.maxTurnToolResultChars);
1154
1174
  return { toolResults, aborted };
1155
1175
  }
1156
1176
  function buildToolResults(initialToolResults, toolCalls, resultsById) {
@@ -1193,6 +1213,34 @@ function capToolResults(toolResults, maxToolResultChars) {
1193
1213
  ` + tail;
1194
1214
  }
1195
1215
  }
1216
+ function capTurnToolResults(toolResults, maxTurnToolResultChars) {
1217
+ if (!maxTurnToolResultChars) return;
1218
+ const textResults = toolResults.filter(
1219
+ (toolResult) => typeof toolResult.content === "string"
1220
+ );
1221
+ const total = textResults.reduce((sum, toolResult) => sum + toolResult.content.length, 0);
1222
+ if (total <= maxTurnToolResultChars) return;
1223
+ const bySize = [...textResults].sort((a, b) => a.content.length - b.content.length);
1224
+ let remaining = maxTurnToolResultChars;
1225
+ let left = bySize.length;
1226
+ for (const toolResult of bySize) {
1227
+ const fairShare = Math.floor(remaining / left);
1228
+ left--;
1229
+ if (toolResult.content.length <= fairShare) {
1230
+ remaining -= toolResult.content.length;
1231
+ continue;
1232
+ }
1233
+ remaining -= fairShare;
1234
+ const headChars = Math.floor(fairShare * 0.7);
1235
+ const tailChars = fairShare - headChars;
1236
+ const omitted = toolResult.content.length - fairShare;
1237
+ toolResult.content = toolResult.content.slice(0, headChars) + `
1238
+
1239
+ [... ${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
+
1241
+ ` + (tailChars > 0 ? toolResult.content.slice(-tailChars) : "");
1242
+ }
1243
+ }
1196
1244
  function normalizeToolResult(raw) {
1197
1245
  return typeof raw === "string" ? { content: raw } : raw;
1198
1246
  }