Skip to content

Commit 93ba60c

Browse files
committed
fix: probe stream for deferred provider errors to enable model fallback chain
AI SDK v4.3.16 resolves streamText() immediately even on 404/deprecated model errors. Errors only surface as stream events (type: 'error'), never as rejected promises. This fix uses ReadableStream.tee() to peek at the first event and throw if it's an error, triggering the existing catch/fallback chain.
1 parent 77899e0 commit 93ba60c

1 file changed

Lines changed: 30 additions & 0 deletions

File tree

app/lib/.server/llm/stream-text.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,36 @@ ${fileList.map((f) => `- ${f}`).join('\n')}
728728

729729
try {
730730
result = await _streamText(streamParams);
731+
732+
/*
733+
* AI SDK v4 wraps certain provider-level HTTP errors (e.g. 404 for
734+
* deprecated/removed models) into stream error events instead of
735+
* rejecting the streamText() promise. Probe the first stream event
736+
* so the fallback chain below can handle these errors.
737+
*/
738+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
739+
const rawStream = result.fullStream as any;
740+
741+
if (typeof rawStream?.tee === 'function') {
742+
const [probe, consumer] = rawStream.tee();
743+
const reader = probe.getReader();
744+
const { done, value } = await reader.read();
745+
746+
reader.cancel();
747+
748+
if (!done && value?.type === 'error') {
749+
consumer.cancel();
750+
throw value.error ?? new Error('Stream returned an error event');
751+
}
752+
753+
// Replace fullStream with the untouched branch so downstream
754+
// consumers (monitoring IIFE in api.chat.ts) can still iterate.
755+
// mergeIntoDataStream() uses separate internal streams — unaffected.
756+
Object.defineProperty(result, 'fullStream', {
757+
value: consumer,
758+
configurable: true,
759+
});
760+
}
731761
} catch (primaryError: unknown) {
732762
const errorCategory = categorizeLLMError(primaryError);
733763
const primaryLabel = `${provider.name}/${modelDetails.name}`;

0 commit comments

Comments
 (0)