@prestyj/agent 5.12.0 → 5.13.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
@@ -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
  }
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
  }
package/dist/index.js CHANGED
@@ -925,6 +925,7 @@ async function* agentLoop(messages, options) {
925
925
  continue;
926
926
  }
927
927
  }
928
+ const emptyExhausted = !hasActionableContent;
928
929
  emptyResponseRetries = 0;
929
930
  useNonStreamingFallback = false;
930
931
  totalUsage.inputTokens += response.usage.inputTokens;
@@ -938,9 +939,11 @@ async function* agentLoop(messages, options) {
938
939
  if (response.usage.cacheWrite) {
939
940
  totalUsage.cacheWrite = (totalUsage.cacheWrite ?? 0) + response.usage.cacheWrite;
940
941
  }
941
- messages.push(response.message);
942
- latestProviderUsage = response.usage;
943
- usageAnchorIndex = messages.length - 1;
942
+ if (!emptyExhausted) {
943
+ messages.push(response.message);
944
+ latestProviderUsage = response.usage;
945
+ usageAnchorIndex = messages.length - 1;
946
+ }
944
947
  const completedAt = Date.now();
945
948
  const outputTokensPerSecond = providerDurationMs > 0 && response.usage.outputTokens > 0 ? response.usage.outputTokens / (providerDurationMs / 1e3) : void 0;
946
949
  const timing = {
@@ -972,8 +975,15 @@ async function* agentLoop(messages, options) {
972
975
  }
973
976
  consecutivePauses = 0;
974
977
  const allToolCalls = extractToolCalls(response.message.content);
975
- if (response.stopReason !== "tool_use" && allToolCalls.length === 0) {
976
- if (response.stopReason === "max_tokens") {
978
+ if (emptyExhausted || response.stopReason !== "tool_use" && allToolCalls.length === 0) {
979
+ if (emptyExhausted) {
980
+ diag("empty_response_exhausted", {
981
+ provider: options.provider,
982
+ model: options.model,
983
+ stopReason: response.stopReason
984
+ });
985
+ yield { type: "truncated", reason: "empty_response", continued: false };
986
+ } else if (response.stopReason === "max_tokens") {
977
987
  if (maxTokensContinuations < MAX_OUTPUT_CONTINUATIONS) {
978
988
  maxTokensContinuations++;
979
989
  diag("max_tokens_continuation", {
@@ -1195,6 +1205,7 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1195
1205
  let resultContent;
1196
1206
  let details;
1197
1207
  let isError = false;
1208
+ let invalidArgAttempt;
1198
1209
  const tool = options.toolMap.get(toolCall.name);
1199
1210
  if (!tool) {
1200
1211
  resultContent = `Unknown tool: ${toolCall.name}`;
@@ -1232,6 +1243,7 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1232
1243
  const failureKey = `${toolCall.name}:${prettyError}`;
1233
1244
  const failureCount = (options.invalidToolArgumentCounts.get(failureKey) ?? 0) + 1;
1234
1245
  options.invalidToolArgumentCounts.set(failureKey, failureCount);
1246
+ invalidArgAttempt = failureCount;
1235
1247
  resultContent = `Invalid arguments for tool \`${toolCall.name}\`:
1236
1248
  ` + prettyError + "\nRe-issue the call with each field as the correct type.";
1237
1249
  if (failureCount >= 3) {
@@ -1262,7 +1274,8 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
1262
1274
  result: toolResultPreview(resultContent),
1263
1275
  details,
1264
1276
  isError,
1265
- durationMs
1277
+ durationMs,
1278
+ ...invalidArgAttempt === void 0 ? {} : { invalidArgAttempt }
1266
1279
  });
1267
1280
  return { toolCallId: toolCall.id, content: resultContent, isError };
1268
1281
  }