@kenkaiiii/gg-agent 5.49.4 → 5.49.6

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 CHANGED
@@ -61,6 +61,60 @@ function isLocalBackendUrl(baseUrl) {
61
61
  return false;
62
62
  }
63
63
 
64
+ // src/output-ceiling.ts
65
+ var TTL_MS = 24 * 60 * 60 * 1e3;
66
+ var MIN_PLAUSIBLE_CEILING = 256;
67
+ var MAX_PLAUSIBLE_CEILING = 1e7;
68
+ var ceilings = /* @__PURE__ */ new Map();
69
+ function outputRouteKey(route) {
70
+ return `${route.provider}\0${route.baseUrl ?? "default"}\0${route.model}`;
71
+ }
72
+ function parseOutputTokenCeiling(err) {
73
+ if (!(err instanceof Error)) return null;
74
+ const msg = err.message;
75
+ if (!/max_tokens|max output tokens|output tokens|completion tokens/i.test(msg)) return null;
76
+ if (err.statusCode === 402) return null;
77
+ if (/credit|billing|payment|insufficient|balance|quota/i.test(msg)) return null;
78
+ const patterns = [
79
+ // Anthropic: "max_tokens: 100000 > 64000, which is the maximum allowed…"
80
+ /max_tokens:\s*\d+\s*>\s*(\d+)/i,
81
+ // OpenAI: "…this model supports at most 16384 completion tokens"
82
+ /(?:at most|maximum of|limit of)\s+([\d,_]+)\s*(?:output|completion)?\s*tokens/i,
83
+ // Generic: "max output tokens is 8192" / "max_tokens must be <= 4096"
84
+ /(?:max_tokens|max output tokens)\b[^\d]{0,30}?([\d,_]+)/i
85
+ ];
86
+ for (const pattern of patterns) {
87
+ const raw = msg.match(pattern)?.[1];
88
+ if (raw === void 0) continue;
89
+ const limit = Number(raw.replace(/[,_]/g, ""));
90
+ if (Number.isFinite(limit) && limit >= MIN_PLAUSIBLE_CEILING && limit <= MAX_PLAUSIBLE_CEILING) {
91
+ return limit;
92
+ }
93
+ }
94
+ return null;
95
+ }
96
+ function rememberOutputCeiling(key, limit) {
97
+ const known = outputTokenCeiling(key);
98
+ ceilings.set(key, {
99
+ limit: known === void 0 ? limit : Math.min(known, limit),
100
+ expiresAt: Date.now() + TTL_MS
101
+ });
102
+ }
103
+ function outputTokenCeiling(key) {
104
+ const entry = ceilings.get(key);
105
+ if (!entry) return void 0;
106
+ if (entry.expiresAt <= Date.now()) {
107
+ ceilings.delete(key);
108
+ return void 0;
109
+ }
110
+ return entry.limit;
111
+ }
112
+ function clampOutputTokens(key, requested) {
113
+ const ceiling = outputTokenCeiling(key);
114
+ if (ceiling === void 0) return requested;
115
+ return requested === void 0 ? ceiling : Math.min(requested, ceiling);
116
+ }
117
+
64
118
  // src/agent-loop.ts
65
119
  var DEFAULT_MAX_TURNS = 300;
66
120
  var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
@@ -297,6 +351,16 @@ async function* agentLoop(messages, options) {
297
351
  const MAX_OUTPUT_CONTINUATIONS = 2;
298
352
  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.]";
299
353
  let maxTokensContinuations = 0;
354
+ let providerCalls = 0;
355
+ let nonStreamingCalls = 0;
356
+ let warnedNonStreaming = false;
357
+ const MAX_OUTPUT_CEILING_RETRIES = 1;
358
+ let outputCeilingRetries = 0;
359
+ const ceilingKey = outputRouteKey({
360
+ provider: options.provider,
361
+ model: options.model,
362
+ baseUrl: options.baseUrl
363
+ });
300
364
  const OVERLOAD_BASE_DELAY_MS = 2e3;
301
365
  const OVERLOAD_MAX_DELAY_MS = 3e4;
302
366
  const STREAM_FIRST_EVENT_TIMEOUT_MS = 45e3;
@@ -424,6 +488,18 @@ async function* agentLoop(messages, options) {
424
488
  }, hardTimeoutMs);
425
489
  try {
426
490
  diag("stream_call", { nonStreaming: useNonStreamingFallback });
491
+ providerCalls++;
492
+ if (useNonStreamingFallback) nonStreamingCalls++;
493
+ if (!warnedNonStreaming && nonStreamingCalls >= 3) {
494
+ warnedNonStreaming = true;
495
+ diag("non_streaming_session", {
496
+ nonStreamingCalls,
497
+ providerCalls,
498
+ provider: options.provider,
499
+ model: options.model,
500
+ impact: "streaming is disabled for this session after repeated stalls: failed turns are re-billed in full instead of resuming from partial output, and replies appear only when complete"
501
+ });
502
+ }
427
503
  streamCallStart = Date.now();
428
504
  providerAttemptStartedAt = streamCallStart;
429
505
  let liveApiKey = options.apiKey;
@@ -449,7 +525,9 @@ async function* agentLoop(messages, options) {
449
525
  serverTools: options.serverTools,
450
526
  toolChoice: options.toolChoice,
451
527
  webSearch: options.webSearch,
452
- maxTokens: options.maxTokens,
528
+ // Clamped to whatever ceiling this route has already rejected us for
529
+ // (identity when nothing has been learned).
530
+ maxTokens: clampOutputTokens(ceilingKey, options.maxTokens),
453
531
  temperature: options.temperature,
454
532
  thinking: options.thinking,
455
533
  apiKey: liveApiKey,
@@ -599,6 +677,29 @@ async function* agentLoop(messages, options) {
599
677
  });
600
678
  throw err;
601
679
  }
680
+ const statedCeiling = parseOutputTokenCeiling(err);
681
+ if (statedCeiling !== null) {
682
+ rememberOutputCeiling(ceilingKey, statedCeiling);
683
+ diag("output_ceiling_learned", {
684
+ ceiling: statedCeiling,
685
+ requested: options.maxTokens,
686
+ provider: options.provider,
687
+ model: options.model
688
+ });
689
+ if (outputCeilingRetries < MAX_OUTPUT_CEILING_RETRIES) {
690
+ outputCeilingRetries++;
691
+ yield {
692
+ type: "retry",
693
+ reason: "provider_error",
694
+ attempt: outputCeilingRetries,
695
+ maxAttempts: MAX_OUTPUT_CEILING_RETRIES,
696
+ delayMs: 0,
697
+ silent: true
698
+ };
699
+ turn--;
700
+ continue;
701
+ }
702
+ }
602
703
  if (isContextOverflow(err)) {
603
704
  const overflowDetails = extractContextOverflowDetails(err);
604
705
  diag("context_overflow_detected", {