@genesislcap/foundation-ai 14.488.0 → 14.488.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.
@@ -28,26 +28,36 @@ export interface AnthropicTransportConfig {
28
28
  maxTokens?: number;
29
29
  }
30
30
  /**
31
- * Thrown when a response stops at `stop_reason: 'max_tokens'` while it still
32
- * carries tool calls. The model ran out of its output-token budget mid-stream,
33
- * so the truncated tool call's argument JSON was cut off before it closed the
34
- * arguments are incomplete and the call is unusable (e.g. a `vfs_write` whose
35
- * `content` never finished serializing). Rather than hand a corrupt tool call to
36
- * the caller which silently runs with missing args and tends to be retried into
37
- * an identical wall the transport raises this so the failure is loud and
38
- * diagnosable.
31
+ * Thrown when a response was cut off at the output-token limit (Anthropic
32
+ * `stop_reason: 'max_tokens'`, Gemini `finishReason: 'MAX_TOKENS'`) in a way
33
+ * that leaves nothing safe to keep. Both transports raise it when the truncated
34
+ * turn carries tool calls: the model ran out of its output-token budget
35
+ * mid-stream, so the cut-off call's arguments are incomplete or untrustworthy
36
+ * (e.g. a `vfs_write` whose `content` never finished serializing) rather than
37
+ * hand a corrupt tool call to the caller, which silently runs with missing args
38
+ * and tends to be retried into an identical wall, the failure is loud and
39
+ * diagnosable. The Gemini transport also raises it when truncation leaves no
40
+ * usable answer text (a blank or thinking-only response), which would otherwise
41
+ * be misclassified — and pointlessly retried — as an "empty" response.
39
42
  *
40
43
  * It is deterministic: re-issuing the same request hits the same cap. The remedy
41
- * is to raise the provider's `maxTokens` (see {@link AnthropicAIConfig.maxTokens})
42
- * or to split the work into smaller outputs — not to retry verbatim.
44
+ * is to raise the provider's `maxTokens` where configurable (see
45
+ * {@link AnthropicAIConfig.maxTokens}) or to split the work into smaller
46
+ * outputs — not to retry verbatim.
47
+ *
48
+ * Shared by both transports; it lives here with the transport that introduced it.
43
49
  *
44
50
  * @beta
45
51
  */
46
52
  export declare class ResponseTruncatedError extends Error {
47
53
  /** The model that produced the truncated response. */
48
54
  readonly model: string;
49
- /** The `max_tokens` cap the request was sent with. */
50
- readonly maxTokens: number;
55
+ /**
56
+ * The `max_tokens` cap the request was sent with, when the transport sends
57
+ * one (Anthropic). Undefined when no explicit cap was requested and the
58
+ * model's own output-token limit applied instead (Gemini).
59
+ */
60
+ readonly maxTokens: number | undefined;
51
61
  /** Output tokens generated before truncation, when the usage block is present. */
52
62
  readonly outputTokens: number | undefined;
53
63
  /** Names of the tool call(s) on the truncated turn (the last is the cut-off one). */
@@ -55,8 +65,12 @@ export declare class ResponseTruncatedError extends Error {
55
65
  constructor(
56
66
  /** The model that produced the truncated response. */
57
67
  model: string,
58
- /** The `max_tokens` cap the request was sent with. */
59
- maxTokens: number,
68
+ /**
69
+ * The `max_tokens` cap the request was sent with, when the transport sends
70
+ * one (Anthropic). Undefined when no explicit cap was requested and the
71
+ * model's own output-token limit applied instead (Gemini).
72
+ */
73
+ maxTokens: number | undefined,
60
74
  /** Output tokens generated before truncation, when the usage block is present. */
61
75
  outputTokens: number | undefined,
62
76
  /** Names of the tool call(s) on the truncated turn (the last is the cut-off one). */
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-transport.d.ts","sourceRoot":"","sources":["../../../src/transports/anthropic-transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"anthropic-transport.d.ts","sourceRoot":"","sources":["../../../src/transports/anthropic-transport.ts"],"names":[],"mappings":"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"}
@@ -82,6 +82,21 @@ export declare class GeminiTransport implements AITransport, ChatTransport, Cost
82
82
  private logTokenUsage;
83
83
  private toGeminiContents;
84
84
  private fromGeminiResponse;
85
+ /**
86
+ * Raises {@link ResponseTruncatedError} when a `MAX_TOKENS` finish left nothing
87
+ * safe to keep. A MAX_TOKENS finish means generation was cut off at the
88
+ * output-token limit (no explicit cap is requested, so this is the model's own
89
+ * output ceiling — which thinking tokens share). Truncation is DETERMINISTIC:
90
+ * re-issuing the identical request hits the same limit, so it must not fall
91
+ * through to the caller's empty-response retry. Mirrors the Anthropic
92
+ * transport's contract: with tool calls the cut-off call's args can't be
93
+ * trusted, and with no usable answer text (blank or thinking-only) there is
94
+ * nothing to keep either — the typed truncation error is raised for both.
95
+ * Partial answer TEXT is instead returned by `fromGeminiResponse`, flagged via
96
+ * `responseMeta.finishReason`, for the caller to decide. Usage/cost is logged
97
+ * before this check runs, so the spent tokens stay accounted for.
98
+ */
99
+ private guardMaxTokensTruncation;
85
100
  /**
86
101
  * Log the full shape of a blank or non-STOP response so its cause is legible
87
102
  * without re-deriving it: a thinking-only STOP (substantial `thoughtsTokenCount`,
@@ -96,6 +111,18 @@ export declare class GeminiTransport implements AITransport, ChatTransport, Cost
96
111
  private static readonly MAX_RETRIES;
97
112
  private static readonly RATE_LIMIT_STATUS;
98
113
  private static readonly SERVICE_UNAVAILABLE_STATUS;
114
+ /**
115
+ * 502 BAD_GATEWAY is a cleanly transient fault from an intermediate proxy or load
116
+ * balancer fronting the API — back off and retry it alongside 429/503.
117
+ *
118
+ * Deliberately NOT retried: 500 INTERNAL and 504 DEADLINE_EXCEEDED. Google documents
119
+ * both as also carrying a deterministic cause ("input context is too long" / "prompt
120
+ * too large to process in time"); re-issuing the identical request just re-hits the
121
+ * same wall and burns the whole backoff ladder — the same deterministic-retry trap
122
+ * this transport avoids for MAX_TOKENS, and especially wasteful on the large-context
123
+ * agent path. They fail fast instead.
124
+ */
125
+ private static readonly BAD_GATEWAY_STATUS;
99
126
  private static readonly RETRYABLE_STATUSES;
100
127
  private static readonly BACKOFF_BASE_MS;
101
128
  private post;
@@ -1 +1 @@
1
- {"version":3,"file":"gemini-transport.d.ts","sourceRoot":"","sources":["../../../src/transports/gemini-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC7B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAEV,WAAW,EACX,kBAAkB,EAInB,MAAM,qBAAqB,CAAC;AAyI7B,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA0CD;;;;;;GAMG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM;gBAAtB,aAAa,CAAC,EAAE,MAAM;CAI5C;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAgB,YAAW,WAAW,EAAE,aAAa,EAAE,sBAAsB;IACxF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD;;;;;OAKG;IACH,OAAO,CAAC,eAAe,CAAK;IAC5B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAAK;gBAEnB,MAAM,GAAE,qBAA0B;IAc9C,SAAS,IAAI;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;IAQ/E,iGAAiG;IACjG,eAAe,IAAI,MAAM;IAIzB,mGAAmG;IACnG,kBAAkB,IAAI,MAAM;IAI5B,+FAA+F;IAC/F,iBAAiB,IAAI,IAAI;IAOnB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;IAgBvE,eAAe,CACnB,OAAO,EAAE,WAAW,EAAE,EACtB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,WAAW,CAAC;IAsEvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAa;IACvD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAK;IAEhD;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAgDrB,OAAO,CAAC,gBAAgB;IAmFxB,OAAO,CAAC,kBAAkB;IAoJ1B;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB;IAkC3B,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAK;IACxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAO;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAO;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAGxC;IACF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAQ;YAEjC,IAAI;IA+DlB,OAAO,CAAC,eAAe;CAexB"}
1
+ {"version":3,"file":"gemini-transport.d.ts","sourceRoot":"","sources":["../../../src/transports/gemini-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC7B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAEV,WAAW,EACX,kBAAkB,EAInB,MAAM,qBAAqB,CAAC;AA0I7B,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA0CD;;;;;;GAMG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM;gBAAtB,aAAa,CAAC,EAAE,MAAM;CAI5C;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAgB,YAAW,WAAW,EAAE,aAAa,EAAE,sBAAsB;IACxF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD;;;;;OAKG;IACH,OAAO,CAAC,eAAe,CAAK;IAC5B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAAK;gBAEnB,MAAM,GAAE,qBAA0B;IAc9C,SAAS,IAAI;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;IAQ/E,iGAAiG;IACjG,eAAe,IAAI,MAAM;IAIzB,mGAAmG;IACnG,kBAAkB,IAAI,MAAM;IAI5B,+FAA+F;IAC/F,iBAAiB,IAAI,IAAI;IAOnB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;IAgBvE,eAAe,CACnB,OAAO,EAAE,WAAW,EAAE,EACtB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,WAAW,CAAC;IAsEvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAa;IACvD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAK;IAEhD;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAgDrB,OAAO,CAAC,gBAAgB;IAmFxB,OAAO,CAAC,kBAAkB;IAsJ1B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,wBAAwB;IAkBhC;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB;IAkC3B,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAK;IACxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAO;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAO;IACzD;;;;;;;;;;OAUG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAO;IACjD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAIxC;IACF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAQ;YAEjC,IAAI;IAsElB,OAAO,CAAC,eAAe;CAexB"}
@@ -97,18 +97,24 @@ const ANTHROPIC_CACHE_READ_MULTIPLIER = 0.1;
97
97
  const ANTHROPIC_CACHE_WRITE_5M_MULTIPLIER = 1.25;
98
98
  const ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER = 2;
99
99
  /**
100
- * Thrown when a response stops at `stop_reason: 'max_tokens'` while it still
101
- * carries tool calls. The model ran out of its output-token budget mid-stream,
102
- * so the truncated tool call's argument JSON was cut off before it closed the
103
- * arguments are incomplete and the call is unusable (e.g. a `vfs_write` whose
104
- * `content` never finished serializing). Rather than hand a corrupt tool call to
105
- * the caller which silently runs with missing args and tends to be retried into
106
- * an identical wall the transport raises this so the failure is loud and
107
- * diagnosable.
100
+ * Thrown when a response was cut off at the output-token limit (Anthropic
101
+ * `stop_reason: 'max_tokens'`, Gemini `finishReason: 'MAX_TOKENS'`) in a way
102
+ * that leaves nothing safe to keep. Both transports raise it when the truncated
103
+ * turn carries tool calls: the model ran out of its output-token budget
104
+ * mid-stream, so the cut-off call's arguments are incomplete or untrustworthy
105
+ * (e.g. a `vfs_write` whose `content` never finished serializing) rather than
106
+ * hand a corrupt tool call to the caller, which silently runs with missing args
107
+ * and tends to be retried into an identical wall, the failure is loud and
108
+ * diagnosable. The Gemini transport also raises it when truncation leaves no
109
+ * usable answer text (a blank or thinking-only response), which would otherwise
110
+ * be misclassified — and pointlessly retried — as an "empty" response.
108
111
  *
109
112
  * It is deterministic: re-issuing the same request hits the same cap. The remedy
110
- * is to raise the provider's `maxTokens` (see {@link AnthropicAIConfig.maxTokens})
111
- * or to split the work into smaller outputs — not to retry verbatim.
113
+ * is to raise the provider's `maxTokens` where configurable (see
114
+ * {@link AnthropicAIConfig.maxTokens}) or to split the work into smaller
115
+ * outputs — not to retry verbatim.
116
+ *
117
+ * Shared by both transports; it lives here with the transport that introduced it.
112
118
  *
113
119
  * @beta
114
120
  */
@@ -116,15 +122,19 @@ export class ResponseTruncatedError extends Error {
116
122
  constructor(
117
123
  /** The model that produced the truncated response. */
118
124
  model,
119
- /** The `max_tokens` cap the request was sent with. */
125
+ /**
126
+ * The `max_tokens` cap the request was sent with, when the transport sends
127
+ * one (Anthropic). Undefined when no explicit cap was requested and the
128
+ * model's own output-token limit applied instead (Gemini).
129
+ */
120
130
  maxTokens,
121
131
  /** Output tokens generated before truncation, when the usage block is present. */
122
132
  outputTokens,
123
133
  /** Names of the tool call(s) on the truncated turn (the last is the cut-off one). */
124
134
  toolNames) {
125
- super(`Anthropic response truncated at the max_tokens cap (${maxTokens}) for model ${model}` +
135
+ super(`Response truncated at ${maxTokens != null ? `the max_tokens cap (${maxTokens})` : "the model's output-token limit"} for model ${model}` +
126
136
  (toolNames.length > 0 ? ` while emitting tool call(s): ${toolNames.join(', ')}` : '') +
127
- '. The output exceeds the per-response limit — raise the provider maxTokens or split the work into smaller outputs.');
137
+ '. The output exceeds the per-response limit — raise the provider maxTokens (where configurable) or split the work into smaller outputs.');
128
138
  this.model = model;
129
139
  this.maxTokens = maxTokens;
130
140
  this.outputTokens = outputTokens;
@@ -3,6 +3,7 @@ import { SUPPORTED_GEMINI_MODEL_IDS, } from '../types';
3
3
  import { combineSignals } from '../utils/combine-signals';
4
4
  import { logger } from '../utils/logger';
5
5
  import { scaleTemperature } from '../utils/temperature';
6
+ import { ResponseTruncatedError } from './anthropic-transport';
6
7
  import { repairMalformedFunctionCall } from './gemini-malformed-call';
7
8
  const GEMINI_DIRECT_URL = (model) => `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
8
9
  const DEFAULT_MODEL = 'gemini-2.5-flash-lite';
@@ -460,6 +461,7 @@ export class GeminiTransport {
460
461
  textParts: textParts.length,
461
462
  });
462
463
  }
464
+ this.guardMaxTokensTruncation(finishReason, toolCalls, narration, outputTokens);
463
465
  if (inputTokens != null)
464
466
  base.inputTokens = inputTokens;
465
467
  if (outputTokens != null)
@@ -484,6 +486,29 @@ export class GeminiTransport {
484
486
  }
485
487
  return base;
486
488
  }
489
+ /**
490
+ * Raises {@link ResponseTruncatedError} when a `MAX_TOKENS` finish left nothing
491
+ * safe to keep. A MAX_TOKENS finish means generation was cut off at the
492
+ * output-token limit (no explicit cap is requested, so this is the model's own
493
+ * output ceiling — which thinking tokens share). Truncation is DETERMINISTIC:
494
+ * re-issuing the identical request hits the same limit, so it must not fall
495
+ * through to the caller's empty-response retry. Mirrors the Anthropic
496
+ * transport's contract: with tool calls the cut-off call's args can't be
497
+ * trusted, and with no usable answer text (blank or thinking-only) there is
498
+ * nothing to keep either — the typed truncation error is raised for both.
499
+ * Partial answer TEXT is instead returned by `fromGeminiResponse`, flagged via
500
+ * `responseMeta.finishReason`, for the caller to decide. Usage/cost is logged
501
+ * before this check runs, so the spent tokens stay accounted for.
502
+ */
503
+ guardMaxTokensTruncation(finishReason, toolCalls, narration, outputTokens) {
504
+ if (finishReason !== 'MAX_TOKENS')
505
+ return;
506
+ // A truncated text-only answer is partial but legible — keep it (the driver's
507
+ // emptiness rule is `!content.trim()`, so whitespace-only does NOT count).
508
+ if (toolCalls.length === 0 && narration.trim() !== '')
509
+ return;
510
+ throw new ResponseTruncatedError(this.model, undefined, outputTokens, toolCalls.map((tc) => tc.name));
511
+ }
487
512
  /**
488
513
  * Log the full shape of a blank or non-STOP response so its cause is legible
489
514
  * without re-deriving it: a thinking-only STOP (substantial `thoughtsTokenCount`,
@@ -572,9 +597,19 @@ export class GeminiTransport {
572
597
  dispose();
573
598
  if (GeminiTransport.RETRYABLE_STATUSES.includes(response.status) &&
574
599
  attempt < GeminiTransport.MAX_RETRIES) {
575
- // Exponential backoff: 1s, 2s, 4s, 8s, 16s
576
- yield new Promise((resolve) => {
577
- setTimeout(resolve, GeminiTransport.BACKOFF_BASE_MS * (1 << attempt));
600
+ // Exponential backoff: 1s, 2s, 4s, 8s, 16s — abortable, so a caller cancel
601
+ // mid-wait rejects promptly instead of being ignored until the next fetch.
602
+ yield new Promise((resolve, reject) => {
603
+ const timer = setTimeout(resolve, GeminiTransport.BACKOFF_BASE_MS * (1 << attempt));
604
+ const onAbort = () => {
605
+ var _a;
606
+ clearTimeout(timer);
607
+ reject((_a = signal === null || signal === void 0 ? void 0 : signal.reason) !== null && _a !== void 0 ? _a : new DOMException('The operation was aborted.', 'AbortError'));
608
+ };
609
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted)
610
+ onAbort();
611
+ else
612
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
578
613
  });
579
614
  continue;
580
615
  }
@@ -604,8 +639,21 @@ GeminiTransport.COST_DECIMAL_PLACES = 6;
604
639
  GeminiTransport.MAX_RETRIES = 5;
605
640
  GeminiTransport.RATE_LIMIT_STATUS = 429;
606
641
  GeminiTransport.SERVICE_UNAVAILABLE_STATUS = 503;
642
+ /**
643
+ * 502 BAD_GATEWAY is a cleanly transient fault from an intermediate proxy or load
644
+ * balancer fronting the API — back off and retry it alongside 429/503.
645
+ *
646
+ * Deliberately NOT retried: 500 INTERNAL and 504 DEADLINE_EXCEEDED. Google documents
647
+ * both as also carrying a deterministic cause ("input context is too long" / "prompt
648
+ * too large to process in time"); re-issuing the identical request just re-hits the
649
+ * same wall and burns the whole backoff ladder — the same deterministic-retry trap
650
+ * this transport avoids for MAX_TOKENS, and especially wasteful on the large-context
651
+ * agent path. They fail fast instead.
652
+ */
653
+ GeminiTransport.BAD_GATEWAY_STATUS = 502;
607
654
  GeminiTransport.RETRYABLE_STATUSES = [
608
655
  GeminiTransport.RATE_LIMIT_STATUS,
609
656
  GeminiTransport.SERVICE_UNAVAILABLE_STATUS,
657
+ GeminiTransport.BAD_GATEWAY_STATUS,
610
658
  ];
611
659
  GeminiTransport.BACKOFF_BASE_MS = 1000;
@@ -8816,7 +8816,7 @@
8816
8816
  {
8817
8817
  "kind": "Class",
8818
8818
  "canonicalReference": "@genesislcap/foundation-ai!ResponseTruncatedError:class",
8819
- "docComment": "/**\n * Thrown when a response stops at `stop_reason: 'max_tokens'` while it still carries tool calls. The model ran out of its output-token budget mid-stream, so the truncated tool call's argument JSON was cut off before it closed — the arguments are incomplete and the call is unusable (e.g. a `vfs_write` whose `content` never finished serializing). Rather than hand a corrupt tool call to the caller which silently runs with missing args and tends to be retried into an identical wall the transport raises this so the failure is loud and diagnosable.\n *\n * It is deterministic: re-issuing the same request hits the same cap. The remedy is to raise the provider's `maxTokens` (see {@link AnthropicAIConfig.maxTokens}) or to split the work into smaller outputs — not to retry verbatim.\n *\n * @beta\n */\n",
8819
+ "docComment": "/**\n * Thrown when a response was cut off at the output-token limit (Anthropic `stop_reason: 'max_tokens'`, Gemini `finishReason: 'MAX_TOKENS'`) in a way that leaves nothing safe to keep. Both transports raise it when the truncated turn carries tool calls: the model ran out of its output-token budget mid-stream, so the cut-off call's arguments are incomplete or untrustworthy (e.g. a `vfs_write` whose `content` never finished serializing) rather than hand a corrupt tool call to the caller, which silently runs with missing args and tends to be retried into an identical wall, the failure is loud and diagnosable. The Gemini transport also raises it when truncation leaves no usable answer text (a blank or thinking-only response), which would otherwise be misclassified — and pointlessly retried — as an \"empty\" response.\n *\n * It is deterministic: re-issuing the same request hits the same cap. The remedy is to raise the provider's `maxTokens` where configurable (see {@link AnthropicAIConfig.maxTokens}) or to split the work into smaller outputs — not to retry verbatim.\n *\n * Shared by both transports; it lives here with the transport that introduced it.\n *\n * @beta\n */\n",
8820
8820
  "excerptTokens": [
8821
8821
  {
8822
8822
  "kind": "Content",
@@ -8857,7 +8857,7 @@
8857
8857
  },
8858
8858
  {
8859
8859
  "kind": "Content",
8860
- "text": "number"
8860
+ "text": "number | undefined"
8861
8861
  },
8862
8862
  {
8863
8863
  "kind": "Content",
@@ -8921,7 +8921,7 @@
8921
8921
  {
8922
8922
  "kind": "Property",
8923
8923
  "canonicalReference": "@genesislcap/foundation-ai!ResponseTruncatedError#maxTokens:member",
8924
- "docComment": "/**\n * The `max_tokens` cap the request was sent with.\n */\n",
8924
+ "docComment": "/**\n * The `max_tokens` cap the request was sent with, when the transport sends one (Anthropic). Undefined when no explicit cap was requested and the model's own output-token limit applied instead (Gemini).\n */\n",
8925
8925
  "excerptTokens": [
8926
8926
  {
8927
8927
  "kind": "Content",
@@ -8929,7 +8929,7 @@
8929
8929
  },
8930
8930
  {
8931
8931
  "kind": "Content",
8932
- "text": "number"
8932
+ "text": "number | undefined"
8933
8933
  },
8934
8934
  {
8935
8935
  "kind": "Content",
@@ -1503,6 +1503,21 @@ export declare class GeminiTransport implements AITransport, ChatTransport, Cost
1503
1503
  private logTokenUsage;
1504
1504
  private toGeminiContents;
1505
1505
  private fromGeminiResponse;
1506
+ /**
1507
+ * Raises {@link ResponseTruncatedError} when a `MAX_TOKENS` finish left nothing
1508
+ * safe to keep. A MAX_TOKENS finish means generation was cut off at the
1509
+ * output-token limit (no explicit cap is requested, so this is the model's own
1510
+ * output ceiling — which thinking tokens share). Truncation is DETERMINISTIC:
1511
+ * re-issuing the identical request hits the same limit, so it must not fall
1512
+ * through to the caller's empty-response retry. Mirrors the Anthropic
1513
+ * transport's contract: with tool calls the cut-off call's args can't be
1514
+ * trusted, and with no usable answer text (blank or thinking-only) there is
1515
+ * nothing to keep either — the typed truncation error is raised for both.
1516
+ * Partial answer TEXT is instead returned by `fromGeminiResponse`, flagged via
1517
+ * `responseMeta.finishReason`, for the caller to decide. Usage/cost is logged
1518
+ * before this check runs, so the spent tokens stay accounted for.
1519
+ */
1520
+ private guardMaxTokensTruncation;
1506
1521
  /**
1507
1522
  * Log the full shape of a blank or non-STOP response so its cause is legible
1508
1523
  * without re-deriving it: a thinking-only STOP (substantial `thoughtsTokenCount`,
@@ -1517,6 +1532,18 @@ export declare class GeminiTransport implements AITransport, ChatTransport, Cost
1517
1532
  private static readonly MAX_RETRIES;
1518
1533
  private static readonly RATE_LIMIT_STATUS;
1519
1534
  private static readonly SERVICE_UNAVAILABLE_STATUS;
1535
+ /**
1536
+ * 502 BAD_GATEWAY is a cleanly transient fault from an intermediate proxy or load
1537
+ * balancer fronting the API — back off and retry it alongside 429/503.
1538
+ *
1539
+ * Deliberately NOT retried: 500 INTERNAL and 504 DEADLINE_EXCEEDED. Google documents
1540
+ * both as also carrying a deterministic cause ("input context is too long" / "prompt
1541
+ * too large to process in time"); re-issuing the identical request just re-hits the
1542
+ * same wall and burns the whole backoff ladder — the same deterministic-retry trap
1543
+ * this transport avoids for MAX_TOKENS, and especially wasteful on the large-context
1544
+ * agent path. They fail fast instead.
1545
+ */
1546
+ private static readonly BAD_GATEWAY_STATUS;
1520
1547
  private static readonly RETRYABLE_STATUSES;
1521
1548
  private static readonly BACKOFF_BASE_MS;
1522
1549
  private post;
@@ -1866,26 +1893,36 @@ export declare interface ResolveAIConfigOptions {
1866
1893
  }
1867
1894
 
1868
1895
  /**
1869
- * Thrown when a response stops at `stop_reason: 'max_tokens'` while it still
1870
- * carries tool calls. The model ran out of its output-token budget mid-stream,
1871
- * so the truncated tool call's argument JSON was cut off before it closed the
1872
- * arguments are incomplete and the call is unusable (e.g. a `vfs_write` whose
1873
- * `content` never finished serializing). Rather than hand a corrupt tool call to
1874
- * the caller which silently runs with missing args and tends to be retried into
1875
- * an identical wall the transport raises this so the failure is loud and
1876
- * diagnosable.
1896
+ * Thrown when a response was cut off at the output-token limit (Anthropic
1897
+ * `stop_reason: 'max_tokens'`, Gemini `finishReason: 'MAX_TOKENS'`) in a way
1898
+ * that leaves nothing safe to keep. Both transports raise it when the truncated
1899
+ * turn carries tool calls: the model ran out of its output-token budget
1900
+ * mid-stream, so the cut-off call's arguments are incomplete or untrustworthy
1901
+ * (e.g. a `vfs_write` whose `content` never finished serializing) rather than
1902
+ * hand a corrupt tool call to the caller, which silently runs with missing args
1903
+ * and tends to be retried into an identical wall, the failure is loud and
1904
+ * diagnosable. The Gemini transport also raises it when truncation leaves no
1905
+ * usable answer text (a blank or thinking-only response), which would otherwise
1906
+ * be misclassified — and pointlessly retried — as an "empty" response.
1877
1907
  *
1878
1908
  * It is deterministic: re-issuing the same request hits the same cap. The remedy
1879
- * is to raise the provider's `maxTokens` (see {@link AnthropicAIConfig.maxTokens})
1880
- * or to split the work into smaller outputs — not to retry verbatim.
1909
+ * is to raise the provider's `maxTokens` where configurable (see
1910
+ * {@link AnthropicAIConfig.maxTokens}) or to split the work into smaller
1911
+ * outputs — not to retry verbatim.
1912
+ *
1913
+ * Shared by both transports; it lives here with the transport that introduced it.
1881
1914
  *
1882
1915
  * @beta
1883
1916
  */
1884
1917
  export declare class ResponseTruncatedError extends Error {
1885
1918
  /** The model that produced the truncated response. */
1886
1919
  readonly model: string;
1887
- /** The `max_tokens` cap the request was sent with. */
1888
- readonly maxTokens: number;
1920
+ /**
1921
+ * The `max_tokens` cap the request was sent with, when the transport sends
1922
+ * one (Anthropic). Undefined when no explicit cap was requested and the
1923
+ * model's own output-token limit applied instead (Gemini).
1924
+ */
1925
+ readonly maxTokens: number | undefined;
1889
1926
  /** Output tokens generated before truncation, when the usage block is present. */
1890
1927
  readonly outputTokens: number | undefined;
1891
1928
  /** Names of the tool call(s) on the truncated turn (the last is the cut-off one). */
@@ -1893,8 +1930,12 @@ export declare class ResponseTruncatedError extends Error {
1893
1930
  constructor(
1894
1931
  /** The model that produced the truncated response. */
1895
1932
  model: string,
1896
- /** The `max_tokens` cap the request was sent with. */
1897
- maxTokens: number,
1933
+ /**
1934
+ * The `max_tokens` cap the request was sent with, when the transport sends
1935
+ * one (Anthropic). Undefined when no explicit cap was requested and the
1936
+ * model's own output-token limit applied instead (Gemini).
1937
+ */
1938
+ maxTokens: number | undefined,
1898
1939
  /** Output tokens generated before truncation, when the usage block is present. */
1899
1940
  outputTokens: number | undefined,
1900
1941
  /** Names of the tool call(s) on the truncated turn (the last is the cut-off one). */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/foundation-ai",
3
3
  "description": "Genesis Foundation AI - Provider-agnostic AI configuration and shared utilities",
4
- "version": "14.488.0",
4
+ "version": "14.488.1",
5
5
  "sideEffects": false,
6
6
  "license": "SEE LICENSE IN license.txt",
7
7
  "main": "dist/esm/index.js",
@@ -52,17 +52,17 @@
52
52
  }
53
53
  },
54
54
  "devDependencies": {
55
- "@genesislcap/foundation-testing": "14.488.0",
56
- "@genesislcap/genx": "14.488.0",
57
- "@genesislcap/rollup-builder": "14.488.0",
58
- "@genesislcap/ts-builder": "14.488.0",
59
- "@genesislcap/uvu-playwright-builder": "14.488.0",
60
- "@genesislcap/vite-builder": "14.488.0",
61
- "@genesislcap/webpack-builder": "14.488.0"
55
+ "@genesislcap/foundation-testing": "14.488.1",
56
+ "@genesislcap/genx": "14.488.1",
57
+ "@genesislcap/rollup-builder": "14.488.1",
58
+ "@genesislcap/ts-builder": "14.488.1",
59
+ "@genesislcap/uvu-playwright-builder": "14.488.1",
60
+ "@genesislcap/vite-builder": "14.488.1",
61
+ "@genesislcap/webpack-builder": "14.488.1"
62
62
  },
63
63
  "dependencies": {
64
- "@genesislcap/foundation-logger": "14.488.0",
65
- "@genesislcap/foundation-utils": "14.488.0",
64
+ "@genesislcap/foundation-logger": "14.488.1",
65
+ "@genesislcap/foundation-utils": "14.488.1",
66
66
  "@microsoft/fast-foundation": "2.50.0"
67
67
  },
68
68
  "repository": {
@@ -73,5 +73,5 @@
73
73
  "publishConfig": {
74
74
  "access": "public"
75
75
  },
76
- "gitHead": "621daeb7e3612007d300f8510bb6d12b80a5cb53"
76
+ "gitHead": "59f08be328928035b4591fa97da580cc650f96c3"
77
77
  }