@kenkaiiii/gg-agent 5.25.0 → 5.26.1

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
@@ -87,6 +87,21 @@ interface AgentMaxTurnsEvent {
87
87
  totalTurns: number;
88
88
  maxTurns: number;
89
89
  }
90
+ /**
91
+ * Emitted when the loop was about to stop on an exhausted turn budget but the
92
+ * host granted an extension instead. The effective budget is raised and the
93
+ * loop continues with a continuation prompt, so this is NOT terminal — unlike
94
+ * `max_turns`, which still fires if the extended budget is also spent.
95
+ */
96
+ interface AgentTurnBudgetExtendedEvent {
97
+ type: "turn_budget_extended";
98
+ /** Turn number at which the budget was exhausted. */
99
+ turn: number;
100
+ /** New effective `maxTurns` after the extension. */
101
+ grantedTurns: number;
102
+ /** 1-based extension count for this run. */
103
+ extension: number;
104
+ }
90
105
  /**
91
106
  * Warning signal emitted when a turn ended on a non-clean stop reason —
92
107
  * `max_tokens` (output clipped at the model's output-token limit), `refusal`,
@@ -148,7 +163,7 @@ interface AgentFollowUpMessageEvent {
148
163
  type: "follow_up_message";
149
164
  content: Message["content"];
150
165
  }
151
- type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTruncatedEvent | AgentErrorEvent;
166
+ type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTurnBudgetExtendedEvent | AgentTruncatedEvent | AgentErrorEvent;
152
167
  interface TransformContextOptions {
153
168
  /** Force a transform after the provider reports context overflow. */
154
169
  force?: boolean;
@@ -168,6 +183,12 @@ interface AgentOptions {
168
183
  /** Control whether tools may/must be called, or select a named tool when supported. */
169
184
  toolChoice?: StreamOptions["toolChoice"];
170
185
  maxTurns?: number;
186
+ /**
187
+ * How many times `onTurnBudgetExhausted` may grant extra turns in one run.
188
+ * Each grant raises the effective budget by the original `maxTurns`.
189
+ * Default: 2. Set 0 to disable extensions entirely.
190
+ */
191
+ maxTurnExtensions?: number;
171
192
  maxTokens?: number;
172
193
  temperature?: number;
173
194
  thinking?: StreamOptions["thinking"];
@@ -234,6 +255,18 @@ interface AgentOptions {
234
255
  * on read.
235
256
  */
236
257
  getFollowUpMessages?: () => Promise<Message[] | null> | Message[] | null;
258
+ /**
259
+ * Consulted when a tool-running turn exhausts the turn budget mid-task,
260
+ * before the loop emits the terminal `max_turns` event. Return true to grant
261
+ * another `maxTurns` worth of turns; false (the default when unset) keeps
262
+ * today's hard cut-off. Hosts should only grant on evidence of progress —
263
+ * extending a spinning agent just buys it more tokens to spin with.
264
+ */
265
+ onTurnBudgetExhausted?: (ctx: {
266
+ turn: number;
267
+ maxTurns: number;
268
+ extension: number;
269
+ }) => Promise<boolean> | boolean;
237
270
  }
238
271
  interface AgentResult {
239
272
  message: AssistantMessage;
package/dist/index.d.ts CHANGED
@@ -87,6 +87,21 @@ interface AgentMaxTurnsEvent {
87
87
  totalTurns: number;
88
88
  maxTurns: number;
89
89
  }
90
+ /**
91
+ * Emitted when the loop was about to stop on an exhausted turn budget but the
92
+ * host granted an extension instead. The effective budget is raised and the
93
+ * loop continues with a continuation prompt, so this is NOT terminal — unlike
94
+ * `max_turns`, which still fires if the extended budget is also spent.
95
+ */
96
+ interface AgentTurnBudgetExtendedEvent {
97
+ type: "turn_budget_extended";
98
+ /** Turn number at which the budget was exhausted. */
99
+ turn: number;
100
+ /** New effective `maxTurns` after the extension. */
101
+ grantedTurns: number;
102
+ /** 1-based extension count for this run. */
103
+ extension: number;
104
+ }
90
105
  /**
91
106
  * Warning signal emitted when a turn ended on a non-clean stop reason —
92
107
  * `max_tokens` (output clipped at the model's output-token limit), `refusal`,
@@ -148,7 +163,7 @@ interface AgentFollowUpMessageEvent {
148
163
  type: "follow_up_message";
149
164
  content: Message["content"];
150
165
  }
151
- type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTruncatedEvent | AgentErrorEvent;
166
+ type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTurnBudgetExtendedEvent | AgentTruncatedEvent | AgentErrorEvent;
152
167
  interface TransformContextOptions {
153
168
  /** Force a transform after the provider reports context overflow. */
154
169
  force?: boolean;
@@ -168,6 +183,12 @@ interface AgentOptions {
168
183
  /** Control whether tools may/must be called, or select a named tool when supported. */
169
184
  toolChoice?: StreamOptions["toolChoice"];
170
185
  maxTurns?: number;
186
+ /**
187
+ * How many times `onTurnBudgetExhausted` may grant extra turns in one run.
188
+ * Each grant raises the effective budget by the original `maxTurns`.
189
+ * Default: 2. Set 0 to disable extensions entirely.
190
+ */
191
+ maxTurnExtensions?: number;
171
192
  maxTokens?: number;
172
193
  temperature?: number;
173
194
  thinking?: StreamOptions["thinking"];
@@ -234,6 +255,18 @@ interface AgentOptions {
234
255
  * on read.
235
256
  */
236
257
  getFollowUpMessages?: () => Promise<Message[] | null> | Message[] | null;
258
+ /**
259
+ * Consulted when a tool-running turn exhausts the turn budget mid-task,
260
+ * before the loop emits the terminal `max_turns` event. Return true to grant
261
+ * another `maxTurns` worth of turns; false (the default when unset) keeps
262
+ * today's hard cut-off. Hosts should only grant on evidence of progress —
263
+ * extending a spinning agent just buys it more tokens to spin with.
264
+ */
265
+ onTurnBudgetExhausted?: (ctx: {
266
+ turn: number;
267
+ maxTurns: number;
268
+ extension: number;
269
+ }) => Promise<boolean> | boolean;
237
270
  }
238
271
  interface AgentResult {
239
272
  message: AssistantMessage;
package/dist/index.js CHANGED
@@ -191,6 +191,9 @@ function isTransportFailure(err) {
191
191
  }
192
192
  return false;
193
193
  }
194
+ function turnBudgetContinuationPrompt() {
195
+ return "[You reached the turn limit for this segment but the work is not finished, so you have been granted more turns. Before continuing, state in one or two sentences what is already done and what remains, then keep going from there \u2014 do not restart work that is already complete.]";
196
+ }
194
197
  function abortableSleep(ms, signal) {
195
198
  if (signal?.aborted) {
196
199
  return Promise.reject(new DOMException("Aborted", "AbortError"));
@@ -210,6 +213,9 @@ function abortableSleep(ms, signal) {
210
213
  }
211
214
  async function* agentLoop(messages, options) {
212
215
  const maxTurns = options.maxTurns ?? DEFAULT_MAX_TURNS;
216
+ let effectiveMaxTurns = maxTurns;
217
+ const maxTurnExtensions = Math.max(0, options.maxTurnExtensions ?? 2);
218
+ let turnExtensions = 0;
213
219
  const maxContinuations = options.maxContinuations ?? 5;
214
220
  let toolMap = new Map((options.tools ?? []).map((t) => [t.name, t]));
215
221
  const totalUsage = { inputTokens: 0, outputTokens: 0 };
@@ -262,7 +268,7 @@ async function* agentLoop(messages, options) {
262
268
  let firstProviderEventAt;
263
269
  let providerDurationMs = 0;
264
270
  try {
265
- while (turn < maxTurns) {
271
+ while (turn < effectiveMaxTurns) {
266
272
  options.signal?.throwIfAborted();
267
273
  turn++;
268
274
  if (logicalTurnStartedAt === 0) logicalTurnStartedAt = Date.now();
@@ -975,8 +981,46 @@ async function* agentLoop(messages, options) {
975
981
  }
976
982
  }
977
983
  }
978
- if (turn >= maxTurns) {
979
- hitMaxTurns = true;
984
+ if (turn >= effectiveMaxTurns) {
985
+ let extended = false;
986
+ if (options.onTurnBudgetExhausted && turnExtensions < maxTurnExtensions) {
987
+ const extension = turnExtensions + 1;
988
+ let granted = false;
989
+ try {
990
+ granted = await options.onTurnBudgetExhausted({
991
+ turn,
992
+ maxTurns: effectiveMaxTurns,
993
+ extension
994
+ });
995
+ } catch {
996
+ granted = false;
997
+ }
998
+ if (granted) {
999
+ turnExtensions = extension;
1000
+ effectiveMaxTurns += maxTurns;
1001
+ extended = true;
1002
+ diag("turn_budget_extended", {
1003
+ turn,
1004
+ grantedTurns: effectiveMaxTurns,
1005
+ extension,
1006
+ provider: options.provider,
1007
+ model: options.model
1008
+ });
1009
+ yield {
1010
+ type: "turn_budget_extended",
1011
+ turn,
1012
+ grantedTurns: effectiveMaxTurns,
1013
+ extension
1014
+ };
1015
+ messages.push({
1016
+ role: "user",
1017
+ content: turnBudgetContinuationPrompt()
1018
+ });
1019
+ }
1020
+ }
1021
+ if (!extended) {
1022
+ hitMaxTurns = true;
1023
+ }
980
1024
  }
981
1025
  }
982
1026
  } finally {
@@ -992,14 +1036,15 @@ async function* agentLoop(messages, options) {
992
1036
  if (hitMaxTurns) {
993
1037
  diag("max_turns_reached", {
994
1038
  turn,
995
- maxTurns,
1039
+ maxTurns: effectiveMaxTurns,
1040
+ extensions: turnExtensions,
996
1041
  provider: options.provider,
997
1042
  model: options.model
998
1043
  });
999
1044
  yield {
1000
1045
  type: "max_turns",
1001
1046
  totalTurns: turn,
1002
- maxTurns
1047
+ maxTurns: effectiveMaxTurns
1003
1048
  };
1004
1049
  }
1005
1050
  yield {