@prestyj/agent 5.1.0 → 5.2.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.ts CHANGED
@@ -60,9 +60,21 @@ interface AgentDoneEvent {
60
60
  totalTurns: number;
61
61
  totalUsage: Usage;
62
62
  }
63
+ /**
64
+ * Terminal signal emitted when the loop stops because it exhausted its turn
65
+ * budget (`maxTurns`) mid-task — i.e. the model still wanted to run tools but
66
+ * ran out of turns. Distinguishes a hard cut-off from a clean completion so
67
+ * callers (e.g. the subagent spawner) can tell the parent the output may be
68
+ * incomplete. Yielded immediately before the final `agent_done`.
69
+ */
70
+ interface AgentMaxTurnsEvent {
71
+ type: "max_turns";
72
+ totalTurns: number;
73
+ maxTurns: number;
74
+ }
63
75
  interface AgentRetryEvent {
64
76
  type: "retry";
65
- reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact";
77
+ reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact" | "tool_argument_glitch";
66
78
  attempt: number;
67
79
  maxAttempts: number;
68
80
  delayMs: number;
@@ -101,7 +113,7 @@ interface AgentFollowUpMessageEvent {
101
113
  type: "follow_up_message";
102
114
  content: Message["content"];
103
115
  }
104
- type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentErrorEvent;
116
+ type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentErrorEvent;
105
117
  interface AgentOptions {
106
118
  provider: StreamOptions["provider"];
107
119
  model: string;
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { ZodError, prettifyError } from "zod";
6
6
  import {
7
7
  stream,
8
8
  EventStream,
9
+ EZCoderAIError,
9
10
  isHardBillingMessage
10
11
  } from "@prestyj/ai";
11
12
  var DEFAULT_MAX_TURNS = 300;
@@ -232,6 +233,7 @@ async function* agentLoop(messages, options) {
232
233
  const toolMap = new Map((options.tools ?? []).map((t) => [t.name, t]));
233
234
  const totalUsage = { inputTokens: 0, outputTokens: 0 };
234
235
  let turn = 0;
236
+ let hitMaxTurns = false;
235
237
  let firstTurn = true;
236
238
  let consecutivePauses = 0;
237
239
  let toolPairingRepaired = false;
@@ -242,6 +244,7 @@ async function* agentLoop(messages, options) {
242
244
  let overflowCompactionAttempts = 0;
243
245
  let toolResultTruncationAttempted = false;
244
246
  const invalidToolArgumentCounts = /* @__PURE__ */ new Map();
247
+ let toolArgumentAutoContinueUsed = false;
245
248
  let useNonStreamingFallback = false;
246
249
  const MAX_OVERLOAD_RETRIES = 10;
247
250
  const MAX_EMPTY_RESPONSE_RETRIES = 2;
@@ -814,8 +817,12 @@ async function* agentLoop(messages, options) {
814
817
  }
815
818
  }
816
819
  let fatalToolArgumentError = null;
817
- const markFatalToolArgumentError = (error) => {
820
+ let fatalToolArgumentRecoverable = false;
821
+ let fatalToolArgumentToolName = "";
822
+ const markFatalToolArgumentError = (error, recoverable, toolName) => {
818
823
  fatalToolArgumentError = error;
824
+ fatalToolArgumentRecoverable = recoverable;
825
+ fatalToolArgumentToolName = toolName;
819
826
  };
820
827
  const executionOptions = {
821
828
  signal: options.signal,
@@ -831,8 +838,24 @@ async function* agentLoop(messages, options) {
831
838
  messages.push({ role: "tool", content: executionResult.toolResults });
832
839
  const toolsAborted = executionResult.aborted;
833
840
  if (fatalToolArgumentError) {
834
- yield { type: "error", error: fatalToolArgumentError };
835
- break;
841
+ if (fatalToolArgumentRecoverable && !toolArgumentAutoContinueUsed) {
842
+ toolArgumentAutoContinueUsed = true;
843
+ for (const key of invalidToolArgumentCounts.keys()) {
844
+ if (key.startsWith(`${fatalToolArgumentToolName}:`))
845
+ invalidToolArgumentCounts.delete(key);
846
+ }
847
+ yield {
848
+ type: "retry",
849
+ reason: "tool_argument_glitch",
850
+ attempt: 1,
851
+ maxAttempts: 1,
852
+ delayMs: 0,
853
+ silent: false
854
+ };
855
+ } else {
856
+ yield { type: "error", error: fatalToolArgumentError };
857
+ break;
858
+ }
836
859
  }
837
860
  if (toolsAborted) break;
838
861
  if (options.getSteeringMessages) {
@@ -844,6 +867,9 @@ async function* agentLoop(messages, options) {
844
867
  }
845
868
  }
846
869
  }
870
+ if (turn >= maxTurns) {
871
+ hitMaxTurns = true;
872
+ }
847
873
  }
848
874
  } finally {
849
875
  sanitizeOrphanedServerTools(messages);
@@ -855,6 +881,19 @@ async function* agentLoop(messages, options) {
855
881
  break;
856
882
  }
857
883
  }
884
+ if (hitMaxTurns) {
885
+ diag("max_turns_reached", {
886
+ turn,
887
+ maxTurns,
888
+ provider: options.provider,
889
+ model: options.model
890
+ });
891
+ yield {
892
+ type: "max_turns",
893
+ totalTurns: turn,
894
+ maxTurns
895
+ };
896
+ }
858
897
  yield {
859
898
  type: "agent_done",
860
899
  totalTurns: turn,
@@ -920,10 +959,17 @@ async function executeSingleToolCall(toolCall, options, pushEvent) {
920
959
  resultContent = `Invalid arguments for tool \`${toolCall.name}\`:
921
960
  ` + prettyError + "\nRe-issue the call with each field as the correct type.";
922
961
  if (failureCount >= 3) {
962
+ const recoverable = Object.keys(toolCall.args ?? {}).length === 0;
923
963
  options.markFatalToolArgumentError(
924
- new Error(
925
- `The model repeatedly issued invalid arguments for tool \`${toolCall.name}\`. This is usually an upstream model/tool-calling bug. Your conversation is preserved; send another message or switch models to continue.`
926
- )
964
+ new EZCoderAIError(
965
+ `The model repeatedly issued invalid arguments for tool \`${toolCall.name}\`. This is usually an upstream model/tool-calling bug` + (recoverable ? " (the provider's stream returned empty tool-call arguments)" : "") + `. Your conversation is preserved; send another message or switch models to continue.`,
966
+ {
967
+ source: "provider",
968
+ hint: "This is the model/provider's fault, not a ezcoder bug. " + (recoverable ? "ezcoder already retried automatically once; if it recurs, send another message or switch models." : "Send another message or switch models to continue.")
969
+ }
970
+ ),
971
+ recoverable,
972
+ toolCall.name
927
973
  );
928
974
  }
929
975
  } else {