@yourgpt/llm-sdk 2.6.0-beta.0 → 2.6.0-beta.2

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.
Files changed (64) hide show
  1. package/README.md +189 -0
  2. package/dist/adapters/index.d.mts +11 -13
  3. package/dist/adapters/index.d.ts +11 -13
  4. package/dist/adapters/index.js +610 -186
  5. package/dist/adapters/index.mjs +610 -186
  6. package/dist/audio-BBSwnjo8.d.mts +92 -0
  7. package/dist/audio-BBSwnjo8.d.ts +92 -0
  8. package/dist/{base-tNgbBaSo.d.mts → base-BICaQaRS.d.mts} +8 -3
  9. package/dist/{base-C58Dsr9p.d.ts → base-CpRPB8u8.d.ts} +8 -3
  10. package/dist/fallback/index.d.mts +4 -4
  11. package/dist/fallback/index.d.ts +4 -4
  12. package/dist/fallback/index.js +75 -39
  13. package/dist/fallback/index.mjs +75 -39
  14. package/dist/index.d.mts +18 -35
  15. package/dist/index.d.ts +18 -35
  16. package/dist/index.js +304 -88
  17. package/dist/index.mjs +302 -89
  18. package/dist/providers/anthropic/index.d.mts +3 -3
  19. package/dist/providers/anthropic/index.d.ts +3 -3
  20. package/dist/providers/anthropic/index.js +123 -7
  21. package/dist/providers/anthropic/index.mjs +123 -7
  22. package/dist/providers/azure/index.d.mts +3 -3
  23. package/dist/providers/azure/index.d.ts +3 -3
  24. package/dist/providers/azure/index.js +101 -4
  25. package/dist/providers/azure/index.mjs +101 -4
  26. package/dist/providers/fireworks/index.d.mts +3 -3
  27. package/dist/providers/fireworks/index.d.ts +3 -3
  28. package/dist/providers/fireworks/index.js +559 -178
  29. package/dist/providers/fireworks/index.mjs +559 -178
  30. package/dist/providers/google/index.d.mts +3 -3
  31. package/dist/providers/google/index.d.ts +3 -3
  32. package/dist/providers/google/index.js +559 -178
  33. package/dist/providers/google/index.mjs +559 -178
  34. package/dist/providers/ollama/index.d.mts +4 -4
  35. package/dist/providers/ollama/index.d.ts +4 -4
  36. package/dist/providers/ollama/index.js +87 -2
  37. package/dist/providers/ollama/index.mjs +87 -2
  38. package/dist/providers/openai/index.d.mts +25 -23
  39. package/dist/providers/openai/index.d.ts +25 -23
  40. package/dist/providers/openai/index.js +1397 -481
  41. package/dist/providers/openai/index.mjs +1396 -482
  42. package/dist/providers/openrouter/index.d.mts +3 -3
  43. package/dist/providers/openrouter/index.d.ts +3 -3
  44. package/dist/providers/openrouter/index.js +559 -178
  45. package/dist/providers/openrouter/index.mjs +559 -178
  46. package/dist/providers/togetherai/index.d.mts +3 -3
  47. package/dist/providers/togetherai/index.d.ts +3 -3
  48. package/dist/providers/togetherai/index.js +559 -178
  49. package/dist/providers/togetherai/index.mjs +559 -178
  50. package/dist/providers/xai/index.d.mts +3 -3
  51. package/dist/providers/xai/index.d.ts +3 -3
  52. package/dist/providers/xai/index.js +559 -178
  53. package/dist/providers/xai/index.mjs +559 -178
  54. package/dist/{types-B6dhnguR.d.mts → types-C3RgRbEC.d.mts} +11 -4
  55. package/dist/{types-CCxPmkmK.d.ts → types-DCFqkJAy.d.ts} +11 -4
  56. package/dist/{types-BQ31QIsA.d.ts → types-DHQEMFHA.d.mts} +3 -11
  57. package/dist/{types-BkQCSiIt.d.mts → types-Dg1Bcg1k.d.mts} +127 -3
  58. package/dist/{types-BkQCSiIt.d.ts → types-Dg1Bcg1k.d.ts} +127 -3
  59. package/dist/{types-BSSiJW2o.d.mts → types-Dikj3En4.d.ts} +3 -11
  60. package/dist/yourgpt/index.d.mts +1 -1
  61. package/dist/yourgpt/index.d.ts +1 -1
  62. package/docs/multimodal-and-audio.md +170 -0
  63. package/package.json +117 -49
  64. package/LICENSE +0 -21
@@ -0,0 +1,92 @@
1
+ /** Audio models are independent of chat models and chat streaming events. */
2
+ type SpeechFormat = "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm";
3
+ interface AudioResponseMetadata {
4
+ provider: string;
5
+ modelId: string;
6
+ requestId?: string;
7
+ status?: number;
8
+ }
9
+ interface AudioWarning {
10
+ code: string;
11
+ message: string;
12
+ }
13
+ interface SpeechOptions {
14
+ text: string;
15
+ voice?: string;
16
+ outputFormat?: SpeechFormat;
17
+ speed?: number;
18
+ instructions?: string;
19
+ signal?: AbortSignal;
20
+ providerOptions?: Record<string, Record<string, unknown>>;
21
+ }
22
+ interface GeneratedSpeech {
23
+ audio: {
24
+ uint8Array: Uint8Array;
25
+ readonly base64: string;
26
+ mediaType: string;
27
+ format: SpeechFormat;
28
+ };
29
+ response: AudioResponseMetadata;
30
+ warnings: AudioWarning[];
31
+ usage?: Record<string, unknown>;
32
+ }
33
+ interface SpeechModel {
34
+ readonly provider: string;
35
+ readonly modelId: string;
36
+ doGenerate(options: SpeechOptions): Promise<GeneratedSpeech>;
37
+ }
38
+ interface TranscriptionOptions {
39
+ /** Recorded file bytes, base64, or a Blob. URLs and raw PCM are not fetched/decoded. */
40
+ audio: Uint8Array | ArrayBuffer | Blob | string;
41
+ mediaType?: string;
42
+ filename?: string;
43
+ language?: string;
44
+ prompt?: string;
45
+ signal?: AbortSignal;
46
+ providerOptions?: Record<string, Record<string, unknown>>;
47
+ }
48
+ interface TranscriptSegment {
49
+ text: string;
50
+ start: number;
51
+ end: number;
52
+ speaker?: string;
53
+ }
54
+ interface TranscriptionResult {
55
+ text: string;
56
+ language?: string;
57
+ durationSeconds?: number;
58
+ segments?: TranscriptSegment[];
59
+ words?: Array<{
60
+ word: string;
61
+ start: number;
62
+ end: number;
63
+ }>;
64
+ response: AudioResponseMetadata;
65
+ warnings: AudioWarning[];
66
+ usage?: Record<string, unknown>;
67
+ /** Provider-supplied extras, including log probabilities when requested. */
68
+ providerMetadata?: Record<string, unknown>;
69
+ }
70
+ interface TranscriptionModel {
71
+ readonly provider: string;
72
+ readonly modelId: string;
73
+ doTranscribe(options: TranscriptionOptions): Promise<TranscriptionResult>;
74
+ }
75
+ declare const audioErrorMarker: unique symbol;
76
+ declare class AudioAPIError extends Error {
77
+ readonly code: "invalid_input" | "unsupported_option" | "provider_error" | "invalid_response" | "network_error";
78
+ readonly status?: number | undefined;
79
+ readonly requestId?: string | undefined;
80
+ readonly [audioErrorMarker] = true;
81
+ static [Symbol.hasInstance](value: unknown): boolean;
82
+ readonly name = "AudioAPIError";
83
+ constructor(message: string, code: "invalid_input" | "unsupported_option" | "provider_error" | "invalid_response" | "network_error", status?: number | undefined, requestId?: string | undefined);
84
+ }
85
+ declare function generateSpeech({ model, ...options }: SpeechOptions & {
86
+ model: SpeechModel;
87
+ }): Promise<GeneratedSpeech>;
88
+ declare function transcribe({ model, ...options }: TranscriptionOptions & {
89
+ model: TranscriptionModel;
90
+ }): Promise<TranscriptionResult>;
91
+
92
+ export { AudioAPIError as A, type GeneratedSpeech as G, type SpeechModel as S, type TranscriptionModel as T, type SpeechOptions as a, type TranscriptionOptions as b, type TranscriptionResult as c, type SpeechFormat as d, type TranscriptSegment as e, type AudioResponseMetadata as f, generateSpeech as g, type AudioWarning as h, transcribe as t };
@@ -0,0 +1,92 @@
1
+ /** Audio models are independent of chat models and chat streaming events. */
2
+ type SpeechFormat = "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm";
3
+ interface AudioResponseMetadata {
4
+ provider: string;
5
+ modelId: string;
6
+ requestId?: string;
7
+ status?: number;
8
+ }
9
+ interface AudioWarning {
10
+ code: string;
11
+ message: string;
12
+ }
13
+ interface SpeechOptions {
14
+ text: string;
15
+ voice?: string;
16
+ outputFormat?: SpeechFormat;
17
+ speed?: number;
18
+ instructions?: string;
19
+ signal?: AbortSignal;
20
+ providerOptions?: Record<string, Record<string, unknown>>;
21
+ }
22
+ interface GeneratedSpeech {
23
+ audio: {
24
+ uint8Array: Uint8Array;
25
+ readonly base64: string;
26
+ mediaType: string;
27
+ format: SpeechFormat;
28
+ };
29
+ response: AudioResponseMetadata;
30
+ warnings: AudioWarning[];
31
+ usage?: Record<string, unknown>;
32
+ }
33
+ interface SpeechModel {
34
+ readonly provider: string;
35
+ readonly modelId: string;
36
+ doGenerate(options: SpeechOptions): Promise<GeneratedSpeech>;
37
+ }
38
+ interface TranscriptionOptions {
39
+ /** Recorded file bytes, base64, or a Blob. URLs and raw PCM are not fetched/decoded. */
40
+ audio: Uint8Array | ArrayBuffer | Blob | string;
41
+ mediaType?: string;
42
+ filename?: string;
43
+ language?: string;
44
+ prompt?: string;
45
+ signal?: AbortSignal;
46
+ providerOptions?: Record<string, Record<string, unknown>>;
47
+ }
48
+ interface TranscriptSegment {
49
+ text: string;
50
+ start: number;
51
+ end: number;
52
+ speaker?: string;
53
+ }
54
+ interface TranscriptionResult {
55
+ text: string;
56
+ language?: string;
57
+ durationSeconds?: number;
58
+ segments?: TranscriptSegment[];
59
+ words?: Array<{
60
+ word: string;
61
+ start: number;
62
+ end: number;
63
+ }>;
64
+ response: AudioResponseMetadata;
65
+ warnings: AudioWarning[];
66
+ usage?: Record<string, unknown>;
67
+ /** Provider-supplied extras, including log probabilities when requested. */
68
+ providerMetadata?: Record<string, unknown>;
69
+ }
70
+ interface TranscriptionModel {
71
+ readonly provider: string;
72
+ readonly modelId: string;
73
+ doTranscribe(options: TranscriptionOptions): Promise<TranscriptionResult>;
74
+ }
75
+ declare const audioErrorMarker: unique symbol;
76
+ declare class AudioAPIError extends Error {
77
+ readonly code: "invalid_input" | "unsupported_option" | "provider_error" | "invalid_response" | "network_error";
78
+ readonly status?: number | undefined;
79
+ readonly requestId?: string | undefined;
80
+ readonly [audioErrorMarker] = true;
81
+ static [Symbol.hasInstance](value: unknown): boolean;
82
+ readonly name = "AudioAPIError";
83
+ constructor(message: string, code: "invalid_input" | "unsupported_option" | "provider_error" | "invalid_response" | "network_error", status?: number | undefined, requestId?: string | undefined);
84
+ }
85
+ declare function generateSpeech({ model, ...options }: SpeechOptions & {
86
+ model: SpeechModel;
87
+ }): Promise<GeneratedSpeech>;
88
+ declare function transcribe({ model, ...options }: TranscriptionOptions & {
89
+ model: TranscriptionModel;
90
+ }): Promise<TranscriptionResult>;
91
+
92
+ export { AudioAPIError as A, type GeneratedSpeech as G, type SpeechModel as S, type TranscriptionModel as T, type SpeechOptions as a, type TranscriptionOptions as b, type TranscriptionResult as c, type SpeechFormat as d, type TranscriptSegment as e, type AudioResponseMetadata as f, generateSpeech as g, type AudioWarning as h, transcribe as t };
@@ -1,10 +1,12 @@
1
- import { M as Message, A as ActionDefinition, d as ToolDefinition, R as ResponseFormat, W as WebSearchConfig, P as ProviderToolRuntimeOptions, g as StreamEvent, J as TokenUsage, X as LLMConfig, a6 as MessageAttachment } from './types-BkQCSiIt.mjs';
1
+ import { M as Message, A as ActionDefinition, e as ToolDefinition, O as OpenAITransportOptions, h as OpenAIReasoningEffort, R as ResponseFormat, W as WebSearchConfig, l as ProviderToolRuntimeOptions, i as StreamEvent, P as ProviderMetadata, V as TokenUsage, $ as LLMConfig, c as ToolModelOutput, af as MessageAttachment } from './types-Dg1Bcg1k.mjs';
2
2
 
3
3
  /**
4
4
  * Request-level LLM configuration overrides
5
5
  */
6
6
  interface RequestLLMConfig {
7
+ openai?: OpenAITransportOptions;
7
8
  model?: string;
9
+ reasoningEffort?: OpenAIReasoningEffort;
8
10
  temperature?: number;
9
11
  maxTokens?: number;
10
12
  responseFormat?: ResponseFormat;
@@ -45,6 +47,7 @@ interface ChatCompletionRequest {
45
47
  * Non-streaming completion result
46
48
  */
47
49
  interface CompletionResult {
50
+ providerMetadata?: ProviderMetadata;
48
51
  /** Text content */
49
52
  content: string;
50
53
  /** Tool calls */
@@ -207,7 +210,7 @@ type AnthropicToolUseBlock = {
207
210
  type AnthropicToolResultBlock = {
208
211
  type: "tool_result";
209
212
  tool_use_id: string;
210
- content: string;
213
+ content: string | Array<Record<string, unknown>>;
211
214
  };
212
215
  type AnthropicMessageContent = string | Array<AnthropicContentBlock | AnthropicToolUseBlock | AnthropicToolResultBlock>;
213
216
  /**
@@ -236,6 +239,7 @@ type OpenAIMessage = {
236
239
  content: string | OpenAIContentBlock[];
237
240
  } | {
238
241
  role: "assistant";
242
+ providerMetadata?: ProviderMetadata;
239
243
  content: string | null;
240
244
  tool_calls?: Array<{
241
245
  id: string;
@@ -249,11 +253,12 @@ type OpenAIMessage = {
249
253
  role: "tool";
250
254
  content: string;
251
255
  tool_call_id: string;
256
+ modelOutput?: ToolModelOutput;
252
257
  };
253
258
  /**
254
259
  * Format messages for OpenAI with full tool support
255
260
  * Handles: text, images, tool_calls, and tool results
256
261
  */
257
- declare function formatMessagesForOpenAI(messages: Message[], systemPrompt?: string): OpenAIMessage[];
262
+ declare function formatMessagesForOpenAI(messages: Message[], systemPrompt?: string, preserveProviderMetadata?: boolean): OpenAIMessage[];
258
263
 
259
264
  export { type AdapterFactory as A, type ChatCompletionRequest as C, type LLMAdapter as L, type OpenAIContentBlock as O, type CompletionResult as a, formatTools as b, formatMessagesForAnthropic as c, formatMessagesForOpenAI as d, messageToOpenAIContent as e, formatMessages as f, hasMediaAttachments as g, hasImageAttachments as h, attachmentToAnthropicImage as i, attachmentToAnthropicDocument as j, attachmentToOpenAIImage as k, type AnthropicContentBlock as l, messageToAnthropicContent as m };
@@ -1,10 +1,12 @@
1
- import { M as Message, A as ActionDefinition, d as ToolDefinition, R as ResponseFormat, W as WebSearchConfig, P as ProviderToolRuntimeOptions, g as StreamEvent, J as TokenUsage, X as LLMConfig, a6 as MessageAttachment } from './types-BkQCSiIt.js';
1
+ import { M as Message, A as ActionDefinition, e as ToolDefinition, O as OpenAITransportOptions, h as OpenAIReasoningEffort, R as ResponseFormat, W as WebSearchConfig, l as ProviderToolRuntimeOptions, i as StreamEvent, P as ProviderMetadata, V as TokenUsage, $ as LLMConfig, c as ToolModelOutput, af as MessageAttachment } from './types-Dg1Bcg1k.js';
2
2
 
3
3
  /**
4
4
  * Request-level LLM configuration overrides
5
5
  */
6
6
  interface RequestLLMConfig {
7
+ openai?: OpenAITransportOptions;
7
8
  model?: string;
9
+ reasoningEffort?: OpenAIReasoningEffort;
8
10
  temperature?: number;
9
11
  maxTokens?: number;
10
12
  responseFormat?: ResponseFormat;
@@ -45,6 +47,7 @@ interface ChatCompletionRequest {
45
47
  * Non-streaming completion result
46
48
  */
47
49
  interface CompletionResult {
50
+ providerMetadata?: ProviderMetadata;
48
51
  /** Text content */
49
52
  content: string;
50
53
  /** Tool calls */
@@ -207,7 +210,7 @@ type AnthropicToolUseBlock = {
207
210
  type AnthropicToolResultBlock = {
208
211
  type: "tool_result";
209
212
  tool_use_id: string;
210
- content: string;
213
+ content: string | Array<Record<string, unknown>>;
211
214
  };
212
215
  type AnthropicMessageContent = string | Array<AnthropicContentBlock | AnthropicToolUseBlock | AnthropicToolResultBlock>;
213
216
  /**
@@ -236,6 +239,7 @@ type OpenAIMessage = {
236
239
  content: string | OpenAIContentBlock[];
237
240
  } | {
238
241
  role: "assistant";
242
+ providerMetadata?: ProviderMetadata;
239
243
  content: string | null;
240
244
  tool_calls?: Array<{
241
245
  id: string;
@@ -249,11 +253,12 @@ type OpenAIMessage = {
249
253
  role: "tool";
250
254
  content: string;
251
255
  tool_call_id: string;
256
+ modelOutput?: ToolModelOutput;
252
257
  };
253
258
  /**
254
259
  * Format messages for OpenAI with full tool support
255
260
  * Handles: text, images, tool_calls, and tool results
256
261
  */
257
- declare function formatMessagesForOpenAI(messages: Message[], systemPrompt?: string): OpenAIMessage[];
262
+ declare function formatMessagesForOpenAI(messages: Message[], systemPrompt?: string, preserveProviderMetadata?: boolean): OpenAIMessage[];
258
263
 
259
264
  export { type AdapterFactory as A, type ChatCompletionRequest as C, type LLMAdapter as L, type OpenAIContentBlock as O, type CompletionResult as a, formatTools as b, formatMessagesForAnthropic as c, formatMessagesForOpenAI as d, messageToOpenAIContent as e, formatMessages as f, hasMediaAttachments as g, hasImageAttachments as h, attachmentToAnthropicImage as i, attachmentToAnthropicDocument as j, attachmentToOpenAIImage as k, type AnthropicContentBlock as l, messageToAnthropicContent as m };
@@ -1,7 +1,7 @@
1
- import { L as LLMAdapter } from '../base-tNgbBaSo.mjs';
2
- import { F as FallbackChainConfig, c as FallbackFailure, R as RoutingStore } from '../types-B6dhnguR.mjs';
3
- export { d as FallbackInfo, b as RetryBackoff, e as RetryInfo, a as RoutingStrategy } from '../types-B6dhnguR.mjs';
4
- import '../types-BkQCSiIt.mjs';
1
+ import { L as LLMAdapter } from '../base-BICaQaRS.mjs';
2
+ import { F as FallbackChainConfig, c as FallbackFailure, R as RoutingStore } from '../types-C3RgRbEC.mjs';
3
+ export { d as FallbackInfo, b as RetryBackoff, e as RetryInfo, a as RoutingStrategy } from '../types-C3RgRbEC.mjs';
4
+ import '../types-Dg1Bcg1k.mjs';
5
5
  import 'zod';
6
6
 
7
7
  /**
@@ -1,7 +1,7 @@
1
- import { L as LLMAdapter } from '../base-C58Dsr9p.js';
2
- import { F as FallbackChainConfig, c as FallbackFailure, R as RoutingStore } from '../types-CCxPmkmK.js';
3
- export { d as FallbackInfo, b as RetryBackoff, e as RetryInfo, a as RoutingStrategy } from '../types-CCxPmkmK.js';
4
- import '../types-BkQCSiIt.js';
1
+ import { L as LLMAdapter } from '../base-CpRPB8u8.js';
2
+ import { F as FallbackChainConfig, c as FallbackFailure, R as RoutingStore } from '../types-DCFqkJAy.js';
3
+ export { d as FallbackInfo, b as RetryBackoff, e as RetryInfo, a as RoutingStrategy } from '../types-DCFqkJAy.js';
4
+ import '../types-Dg1Bcg1k.js';
5
5
  import 'zod';
6
6
 
7
7
  /**
@@ -1,5 +1,28 @@
1
1
  'use strict';
2
2
 
3
+ // src/core/provider-error.ts
4
+ function errorFromEvent(event) {
5
+ if (event.cause instanceof Error) return event.cause;
6
+ return Object.assign(new Error(event.message), {
7
+ status: event.status,
8
+ code: event.providerCode ?? event.code,
9
+ param: event.param,
10
+ request_id: event.requestId
11
+ });
12
+ }
13
+ function throwIfAborted(signal) {
14
+ if (!signal?.aborted) return;
15
+ const error = new Error("The operation was aborted");
16
+ error.name = "AbortError";
17
+ throw error;
18
+ }
19
+ function isAbortError(error) {
20
+ if (!(error instanceof Error)) return false;
21
+ return error.name === "AbortError" || error.constructor.name === "APIUserAbortError" || /^(aborted|the operation was aborted|the user aborted a request)/i.test(
22
+ error.message
23
+ );
24
+ }
25
+
3
26
  // src/fallback/errors.ts
4
27
  var FallbackExhaustedError = class extends Error {
5
28
  constructor(failures) {
@@ -74,8 +97,24 @@ function calcDelay(base, attempt, backoff) {
74
97
  if (backoff === "fixed") return base;
75
98
  return base * Math.pow(2, attempt - 1);
76
99
  }
77
- function sleep(ms) {
78
- return new Promise((resolve) => setTimeout(resolve, ms));
100
+ function sleep(ms, signal) {
101
+ throwIfAborted(signal);
102
+ return new Promise((resolve, reject) => {
103
+ const onAbort = () => {
104
+ clearTimeout(timer);
105
+ signal?.removeEventListener("abort", onAbort);
106
+ try {
107
+ throwIfAborted(signal);
108
+ } catch (error) {
109
+ reject(error);
110
+ }
111
+ };
112
+ const timer = setTimeout(() => {
113
+ signal?.removeEventListener("abort", onAbort);
114
+ resolve();
115
+ }, ms);
116
+ signal?.addEventListener("abort", onAbort, { once: true });
117
+ });
79
118
  }
80
119
  var FallbackChain = class {
81
120
  constructor(config) {
@@ -91,7 +130,8 @@ var FallbackChain = class {
91
130
  retryBackoff: config.retryBackoff ?? "exponential",
92
131
  onFallback: config.onFallback,
93
132
  onRetry: config.onRetry,
94
- retryableErrors: config.retryableErrors
133
+ retryableErrors: config.retryableErrors,
134
+ fallbackErrors: config.fallbackErrors
95
135
  };
96
136
  }
97
137
  get provider() {
@@ -111,8 +151,12 @@ var FallbackChain = class {
111
151
  await this._config.store.set(ROUND_ROBIN_KEY, next);
112
152
  }
113
153
  _isRetryable(error) {
154
+ if (isAbortError(error)) return false;
114
155
  return this._config.retryableErrors ? this._config.retryableErrors(error) : defaultIsRetryable(error);
115
156
  }
157
+ _canFallback(error) {
158
+ return !isAbortError(error) && (this._isRetryable(error) || this._config.fallbackErrors?.(error) === true);
159
+ }
116
160
  /**
117
161
  * Try streaming from a single adapter, with per-model retries.
118
162
  *
@@ -125,65 +169,52 @@ var FallbackChain = class {
125
169
  */
126
170
  async *_streamWithRetries(adapter, request, out) {
127
171
  const { retries, retryDelay, retryBackoff, onRetry } = this._config;
128
- const maxAttempts = retries + 1;
129
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
130
- let contentStarted = false;
131
- let failureError = null;
172
+ for (let attempt = 0; attempt <= retries; attempt++) {
173
+ throwIfAborted(request.signal);
174
+ out.retriesAttempted = attempt;
132
175
  try {
133
176
  for await (const chunk of adapter.stream(request)) {
134
- if (chunk.type === "error") {
135
- if (!contentStarted) {
136
- const msg = chunk.message ?? "Unknown error";
137
- failureError = new Error(msg);
138
- break;
139
- }
140
- yield chunk;
141
- return;
142
- }
177
+ throwIfAborted(request.signal);
178
+ if (chunk.type === "error") throw errorFromEvent(chunk);
143
179
  if (chunk.type === "message:start") continue;
144
- contentStarted = true;
180
+ out.contentStarted = true;
145
181
  yield chunk;
146
182
  }
183
+ throwIfAborted(request.signal);
184
+ return;
147
185
  } catch (error) {
148
- if (contentStarted) throw error;
149
- if (!this._isRetryable(error)) throw error;
150
- failureError = error instanceof Error ? error : new Error(String(error));
151
- }
152
- if (failureError === null) return;
153
- if (!this._isRetryable(failureError)) throw failureError;
154
- out.retriesAttempted = attempt - 1;
155
- if (attempt < maxAttempts) {
156
- const delayMs = calcDelay(retryDelay, attempt, retryBackoff);
157
- const retryInfo = {
186
+ throwIfAborted(request.signal);
187
+ if (out.contentStarted || !this._isRetryable(error) || attempt === retries)
188
+ throw error;
189
+ const delayMs = calcDelay(retryDelay, attempt + 1, retryBackoff);
190
+ onRetry?.({
158
191
  model: adapter.model,
159
192
  provider: adapter.provider,
160
- error: failureError,
161
- retryAttempt: attempt,
193
+ error: error instanceof Error ? error : new Error(String(error)),
194
+ retryAttempt: attempt + 1,
162
195
  maxRetries: retries,
163
196
  delayMs
164
- };
165
- onRetry?.(retryInfo);
166
- await sleep(delayMs);
167
- continue;
197
+ });
198
+ await sleep(delayMs, request.signal);
168
199
  }
169
- out.retriesAttempted = retries;
170
- throw failureError;
171
200
  }
172
201
  }
173
202
  async *stream(request) {
174
203
  const { models, onFallback } = this._config;
204
+ throwIfAborted(request.signal);
175
205
  const startIndex = await this._startIndex();
176
206
  const failures = [];
177
207
  for (let i = 0; i < models.length; i++) {
178
208
  const index = (startIndex + i) % models.length;
179
209
  const adapter = models[index];
180
- const out = { retriesAttempted: 0 };
210
+ const out = { retriesAttempted: 0, contentStarted: false };
181
211
  try {
182
212
  yield* this._streamWithRetries(adapter, request, out);
183
213
  await this._advanceIndex(index);
184
214
  return;
185
215
  } catch (error) {
186
- if (!this._isRetryable(error)) throw error;
216
+ throwIfAborted(request.signal);
217
+ if (out.contentStarted || !this._canFallback(error)) throw error;
187
218
  const failure = {
188
219
  model: adapter.model,
189
220
  provider: adapter.provider,
@@ -208,6 +239,7 @@ var FallbackChain = class {
208
239
  }
209
240
  async complete(request) {
210
241
  const { models, onFallback, retries, retryDelay, retryBackoff, onRetry } = this._config;
242
+ throwIfAborted(request.signal);
211
243
  const startIndex = await this._startIndex();
212
244
  const failures = [];
213
245
  for (let i = 0; i < models.length; i++) {
@@ -229,14 +261,18 @@ var FallbackChain = class {
229
261
  let lastError = null;
230
262
  let retriesAttempted = 0;
231
263
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
264
+ throwIfAborted(request.signal);
232
265
  try {
233
266
  const result = await adapter.complete(request);
267
+ throwIfAborted(request.signal);
234
268
  await this._advanceIndex(index);
235
269
  return result;
236
270
  } catch (error) {
237
- if (!this._isRetryable(error)) throw error;
271
+ throwIfAborted(request.signal);
272
+ if (!this._canFallback(error)) throw error;
238
273
  lastError = error instanceof Error ? error : new Error(String(error));
239
274
  retriesAttempted = attempt - 1;
275
+ if (!this._isRetryable(error)) break;
240
276
  if (attempt < maxAttempts) {
241
277
  const delayMs = calcDelay(retryDelay, attempt, retryBackoff);
242
278
  onRetry?.({
@@ -247,7 +283,7 @@ var FallbackChain = class {
247
283
  maxRetries: retries,
248
284
  delayMs
249
285
  });
250
- await sleep(delayMs);
286
+ await sleep(delayMs, request.signal);
251
287
  }
252
288
  }
253
289
  }