Skip to content

Commit 3292fbf

Browse files
committed
feat: default-on auto-fallback in generateText
1 parent 22696af commit 3292fbf

2 files changed

Lines changed: 46 additions & 6 deletions

File tree

src/api/generateText.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,16 +1073,18 @@ export async function generateText(opts: GenerateTextOptions): Promise<GenerateT
10731073
});
10741074
} catch (error) {
10751075
// ── Fallback chain ────────────────────────────────────────────────
1076-
// When the primary provider fails with a retryable error and
1077-
// fallbackProviders are configured, try each fallback in order.
1078-
// The first successful response wins; if all fail, the last error
1079-
// is re-thrown.
1076+
// Resolve fallback chain: caller-supplied wins, undefined triggers
1077+
// auto-build from env keys, empty array explicitly opts out.
1078+
const effectiveFallbacks = opts.fallbackProviders === undefined
1079+
? buildFallbackChain(metricProviderId)
1080+
: opts.fallbackProviders;
1081+
10801082
if (
1081-
opts.fallbackProviders?.length &&
1083+
effectiveFallbacks.length &&
10821084
isRetryableError(error)
10831085
) {
10841086
let lastError = error;
1085-
for (const fb of opts.fallbackProviders) {
1087+
for (const fb of effectiveFallbacks) {
10861088
try {
10871089
opts.onFallback?.(
10881090
lastError instanceof Error ? lastError : new Error(String(lastError)),

src/api/runtime/__tests__/generateText.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,4 +369,42 @@ describe('generateText', () => {
369369
]);
370370
expect(result.text).toBe('I could not execute that tool call.');
371371
});
372+
373+
it('auto-builds fallback chain when fallbackProviders is undefined and primary throws 429', async () => {
374+
hoisted.generateCompletion
375+
.mockRejectedValueOnce(new Error('429 rate limit exceeded'))
376+
.mockResolvedValueOnce({
377+
modelId: 'gpt-4o-mini',
378+
usage: { promptTokens: 5, completionTokens: 3, totalTokens: 8 },
379+
choices: [{ message: { role: 'assistant', content: 'fallback reply' }, finishReason: 'stop' }],
380+
});
381+
382+
process.env.ANTHROPIC_API_KEY = 'test-anthropic-key';
383+
try {
384+
const result = await generateText({
385+
model: 'openai:gpt-4o',
386+
prompt: 'hello',
387+
});
388+
expect(result.text).toBe('fallback reply');
389+
} finally {
390+
delete process.env.ANTHROPIC_API_KEY;
391+
}
392+
});
393+
394+
it('does NOT fallback when fallbackProviders is explicitly []', async () => {
395+
hoisted.generateCompletion.mockRejectedValueOnce(new Error('429 rate limit exceeded'));
396+
397+
process.env.ANTHROPIC_API_KEY = 'test-anthropic-key';
398+
try {
399+
await expect(
400+
generateText({
401+
model: 'openai:gpt-4o',
402+
prompt: 'hello',
403+
fallbackProviders: [],
404+
})
405+
).rejects.toThrow('429');
406+
} finally {
407+
delete process.env.ANTHROPIC_API_KEY;
408+
}
409+
});
372410
});

0 commit comments

Comments
 (0)