@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.
- package/dist/dts/transports/anthropic-transport.d.ts +28 -14
- package/dist/dts/transports/anthropic-transport.d.ts.map +1 -1
- package/dist/dts/transports/gemini-transport.d.ts +27 -0
- package/dist/dts/transports/gemini-transport.d.ts.map +1 -1
- package/dist/esm/transports/anthropic-transport.js +23 -13
- package/dist/esm/transports/gemini-transport.js +51 -3
- package/dist/foundation-ai.api.json +4 -4
- package/dist/foundation-ai.d.ts +55 -14
- package/package.json +11 -11
|
@@ -28,26 +28,36 @@ export interface AnthropicTransportConfig {
|
|
|
28
28
|
maxTokens?: number;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Thrown when a response
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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
|
|
42
|
-
* or to split the work into smaller
|
|
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
|
-
/**
|
|
50
|
-
|
|
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
|
-
/**
|
|
59
|
-
|
|
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":"AAAA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC7B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAGV,WAAW,EACX,kBAAkB,EAGnB,MAAM,qBAAqB,CAAC;AAgH7B,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiGD
|
|
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;
|
|
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
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
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
|
|
111
|
-
* or to split the work into smaller
|
|
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
|
-
/**
|
|
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(`
|
|
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
|
-
|
|
577
|
-
|
|
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
|
|
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",
|
package/dist/foundation-ai.d.ts
CHANGED
|
@@ -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
|
|
1870
|
-
*
|
|
1871
|
-
*
|
|
1872
|
-
*
|
|
1873
|
-
*
|
|
1874
|
-
*
|
|
1875
|
-
*
|
|
1876
|
-
*
|
|
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
|
|
1880
|
-
* or to split the work into smaller
|
|
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
|
-
/**
|
|
1888
|
-
|
|
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
|
-
/**
|
|
1897
|
-
|
|
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.
|
|
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.
|
|
56
|
-
"@genesislcap/genx": "14.488.
|
|
57
|
-
"@genesislcap/rollup-builder": "14.488.
|
|
58
|
-
"@genesislcap/ts-builder": "14.488.
|
|
59
|
-
"@genesislcap/uvu-playwright-builder": "14.488.
|
|
60
|
-
"@genesislcap/vite-builder": "14.488.
|
|
61
|
-
"@genesislcap/webpack-builder": "14.488.
|
|
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.
|
|
65
|
-
"@genesislcap/foundation-utils": "14.488.
|
|
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": "
|
|
76
|
+
"gitHead": "59f08be328928035b4591fa97da580cc650f96c3"
|
|
77
77
|
}
|