@kenkaiiii/gg-agent 5.49.3 → 5.49.5

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.js CHANGED
@@ -35,6 +35,60 @@ function isLocalBackendUrl(baseUrl) {
35
35
  return false;
36
36
  }
37
37
 
38
+ // src/output-ceiling.ts
39
+ var TTL_MS = 24 * 60 * 60 * 1e3;
40
+ var MIN_PLAUSIBLE_CEILING = 256;
41
+ var MAX_PLAUSIBLE_CEILING = 1e7;
42
+ var ceilings = /* @__PURE__ */ new Map();
43
+ function outputRouteKey(route) {
44
+ return `${route.provider}\0${route.baseUrl ?? "default"}\0${route.model}`;
45
+ }
46
+ function parseOutputTokenCeiling(err) {
47
+ if (!(err instanceof Error)) return null;
48
+ const msg = err.message;
49
+ if (!/max_tokens|max output tokens|output tokens|completion tokens/i.test(msg)) return null;
50
+ if (err.statusCode === 402) return null;
51
+ if (/credit|billing|payment|insufficient|balance|quota/i.test(msg)) return null;
52
+ const patterns = [
53
+ // Anthropic: "max_tokens: 100000 > 64000, which is the maximum allowed…"
54
+ /max_tokens:\s*\d+\s*>\s*(\d+)/i,
55
+ // OpenAI: "…this model supports at most 16384 completion tokens"
56
+ /(?:at most|maximum of|limit of)\s+([\d,_]+)\s*(?:output|completion)?\s*tokens/i,
57
+ // Generic: "max output tokens is 8192" / "max_tokens must be <= 4096"
58
+ /(?:max_tokens|max output tokens)\b[^\d]{0,30}?([\d,_]+)/i
59
+ ];
60
+ for (const pattern of patterns) {
61
+ const raw = msg.match(pattern)?.[1];
62
+ if (raw === void 0) continue;
63
+ const limit = Number(raw.replace(/[,_]/g, ""));
64
+ if (Number.isFinite(limit) && limit >= MIN_PLAUSIBLE_CEILING && limit <= MAX_PLAUSIBLE_CEILING) {
65
+ return limit;
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ function rememberOutputCeiling(key, limit) {
71
+ const known = outputTokenCeiling(key);
72
+ ceilings.set(key, {
73
+ limit: known === void 0 ? limit : Math.min(known, limit),
74
+ expiresAt: Date.now() + TTL_MS
75
+ });
76
+ }
77
+ function outputTokenCeiling(key) {
78
+ const entry = ceilings.get(key);
79
+ if (!entry) return void 0;
80
+ if (entry.expiresAt <= Date.now()) {
81
+ ceilings.delete(key);
82
+ return void 0;
83
+ }
84
+ return entry.limit;
85
+ }
86
+ function clampOutputTokens(key, requested) {
87
+ const ceiling = outputTokenCeiling(key);
88
+ if (ceiling === void 0) return requested;
89
+ return requested === void 0 ? ceiling : Math.min(requested, ceiling);
90
+ }
91
+
38
92
  // src/agent-loop.ts
39
93
  var DEFAULT_MAX_TURNS = 300;
40
94
  var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
@@ -271,6 +325,16 @@ async function* agentLoop(messages, options) {
271
325
  const MAX_OUTPUT_CONTINUATIONS = 2;
272
326
  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.]";
273
327
  let maxTokensContinuations = 0;
328
+ let providerCalls = 0;
329
+ let nonStreamingCalls = 0;
330
+ let warnedNonStreaming = false;
331
+ const MAX_OUTPUT_CEILING_RETRIES = 1;
332
+ let outputCeilingRetries = 0;
333
+ const ceilingKey = outputRouteKey({
334
+ provider: options.provider,
335
+ model: options.model,
336
+ baseUrl: options.baseUrl
337
+ });
274
338
  const OVERLOAD_BASE_DELAY_MS = 2e3;
275
339
  const OVERLOAD_MAX_DELAY_MS = 3e4;
276
340
  const STREAM_FIRST_EVENT_TIMEOUT_MS = 45e3;
@@ -398,6 +462,18 @@ async function* agentLoop(messages, options) {
398
462
  }, hardTimeoutMs);
399
463
  try {
400
464
  diag("stream_call", { nonStreaming: useNonStreamingFallback });
465
+ providerCalls++;
466
+ if (useNonStreamingFallback) nonStreamingCalls++;
467
+ if (!warnedNonStreaming && nonStreamingCalls >= 3) {
468
+ warnedNonStreaming = true;
469
+ diag("non_streaming_session", {
470
+ nonStreamingCalls,
471
+ providerCalls,
472
+ provider: options.provider,
473
+ model: options.model,
474
+ 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"
475
+ });
476
+ }
401
477
  streamCallStart = Date.now();
402
478
  providerAttemptStartedAt = streamCallStart;
403
479
  let liveApiKey = options.apiKey;
@@ -423,7 +499,9 @@ async function* agentLoop(messages, options) {
423
499
  serverTools: options.serverTools,
424
500
  toolChoice: options.toolChoice,
425
501
  webSearch: options.webSearch,
426
- maxTokens: options.maxTokens,
502
+ // Clamped to whatever ceiling this route has already rejected us for
503
+ // (identity when nothing has been learned).
504
+ maxTokens: clampOutputTokens(ceilingKey, options.maxTokens),
427
505
  temperature: options.temperature,
428
506
  thinking: options.thinking,
429
507
  apiKey: liveApiKey,
@@ -573,6 +651,29 @@ async function* agentLoop(messages, options) {
573
651
  });
574
652
  throw err;
575
653
  }
654
+ const statedCeiling = parseOutputTokenCeiling(err);
655
+ if (statedCeiling !== null) {
656
+ rememberOutputCeiling(ceilingKey, statedCeiling);
657
+ diag("output_ceiling_learned", {
658
+ ceiling: statedCeiling,
659
+ requested: options.maxTokens,
660
+ provider: options.provider,
661
+ model: options.model
662
+ });
663
+ if (outputCeilingRetries < MAX_OUTPUT_CEILING_RETRIES) {
664
+ outputCeilingRetries++;
665
+ yield {
666
+ type: "retry",
667
+ reason: "provider_error",
668
+ attempt: outputCeilingRetries,
669
+ maxAttempts: MAX_OUTPUT_CEILING_RETRIES,
670
+ delayMs: 0,
671
+ silent: true
672
+ };
673
+ turn--;
674
+ continue;
675
+ }
676
+ }
576
677
  if (isContextOverflow(err)) {
577
678
  const overflowDetails = extractContextOverflowDetails(err);
578
679
  diag("context_overflow_detected", {