@yadimon/prio-llm-router 0.1.0
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/LICENSE +21 -0
- package/README.md +434 -0
- package/dist/index.cjs +1058 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +235 -0
- package/dist/index.d.ts +235 -0
- package/dist/index.js +1047 -0
- package/dist/index.js.map +1 -0
- package/package.json +98 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1058 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var anthropic = require('@ai-sdk/anthropic');
|
|
4
|
+
var cohere = require('@ai-sdk/cohere');
|
|
5
|
+
var deepseek = require('@ai-sdk/deepseek');
|
|
6
|
+
var google = require('@ai-sdk/google');
|
|
7
|
+
var groq = require('@ai-sdk/groq');
|
|
8
|
+
var mistral = require('@ai-sdk/mistral');
|
|
9
|
+
var openai = require('@ai-sdk/openai');
|
|
10
|
+
var openaiCompatible = require('@ai-sdk/openai-compatible');
|
|
11
|
+
var perplexity = require('@ai-sdk/perplexity');
|
|
12
|
+
var togetherai = require('@ai-sdk/togetherai');
|
|
13
|
+
var xai = require('@ai-sdk/xai');
|
|
14
|
+
var ai = require('ai');
|
|
15
|
+
var aiSdkProvider = require('@openrouter/ai-sdk-provider');
|
|
16
|
+
|
|
17
|
+
// src/errors.ts
|
|
18
|
+
var PrioLlmRouterError = class extends Error {
|
|
19
|
+
constructor(message, options) {
|
|
20
|
+
super(message, options);
|
|
21
|
+
this.name = new.target.name;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var RouterConfigurationError = class extends PrioLlmRouterError {
|
|
25
|
+
};
|
|
26
|
+
var AllModelsFailedError = class extends PrioLlmRouterError {
|
|
27
|
+
attempts;
|
|
28
|
+
constructor(attempts, cause) {
|
|
29
|
+
super(buildFailureMessage(attempts), { cause });
|
|
30
|
+
this.attempts = attempts;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
function serializeError(error) {
|
|
34
|
+
if (error instanceof Error) {
|
|
35
|
+
const maybeError = error;
|
|
36
|
+
const serialized = {
|
|
37
|
+
name: error.name,
|
|
38
|
+
message: error.message
|
|
39
|
+
};
|
|
40
|
+
if (maybeError.code !== void 0) {
|
|
41
|
+
serialized.code = maybeError.code;
|
|
42
|
+
}
|
|
43
|
+
const statusCode = maybeError.statusCode ?? maybeError.status;
|
|
44
|
+
if (statusCode !== void 0) {
|
|
45
|
+
serialized.statusCode = statusCode;
|
|
46
|
+
}
|
|
47
|
+
return serialized;
|
|
48
|
+
}
|
|
49
|
+
if (typeof error === "string") {
|
|
50
|
+
return {
|
|
51
|
+
name: "Error",
|
|
52
|
+
message: error
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
name: "UnknownError",
|
|
57
|
+
message: "Unknown router error"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function isAbortError(error) {
|
|
61
|
+
if (!(error instanceof Error)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return error.name === "AbortError" || error.name === "TimeoutError";
|
|
65
|
+
}
|
|
66
|
+
function buildFailureMessage(attempts) {
|
|
67
|
+
const summary = attempts.map((attempt) => {
|
|
68
|
+
const errorMessage = attempt.error?.message ?? "Unknown error";
|
|
69
|
+
return `${attempt.targetName} (${attempt.providerName}/${attempt.model}): ${errorMessage}`;
|
|
70
|
+
}).join("; ");
|
|
71
|
+
return summary ? `All configured model attempts failed. ${summary}` : "All configured model attempts failed.";
|
|
72
|
+
}
|
|
73
|
+
function createDefaultTextGenerationExecutor(options) {
|
|
74
|
+
return new AiSdkTextGenerationExecutor(options?.defaultProviderMaxRetries ?? 0);
|
|
75
|
+
}
|
|
76
|
+
var AiSdkTextGenerationExecutor = class {
|
|
77
|
+
constructor(defaultProviderMaxRetries) {
|
|
78
|
+
this.defaultProviderMaxRetries = defaultProviderMaxRetries;
|
|
79
|
+
}
|
|
80
|
+
providerCache = /* @__PURE__ */ new Map();
|
|
81
|
+
async execute({
|
|
82
|
+
provider,
|
|
83
|
+
model,
|
|
84
|
+
request
|
|
85
|
+
}) {
|
|
86
|
+
const languageModel = this.getLanguageModel(provider, model.model);
|
|
87
|
+
const call = buildBaseTextCallOptions({
|
|
88
|
+
languageModel,
|
|
89
|
+
request,
|
|
90
|
+
defaultProviderMaxRetries: this.defaultProviderMaxRetries
|
|
91
|
+
});
|
|
92
|
+
const result = await ai.generateText(call);
|
|
93
|
+
const output = {
|
|
94
|
+
text: result.text,
|
|
95
|
+
finishReason: result.finishReason ?? null,
|
|
96
|
+
raw: result
|
|
97
|
+
};
|
|
98
|
+
const usage = normalizeUsage(result.usage);
|
|
99
|
+
if (usage) {
|
|
100
|
+
output.usage = usage;
|
|
101
|
+
}
|
|
102
|
+
const warnings = normalizeWarnings(result.warnings);
|
|
103
|
+
if (warnings) {
|
|
104
|
+
output.warnings = warnings;
|
|
105
|
+
}
|
|
106
|
+
return output;
|
|
107
|
+
}
|
|
108
|
+
async stream({
|
|
109
|
+
provider,
|
|
110
|
+
model,
|
|
111
|
+
request
|
|
112
|
+
}) {
|
|
113
|
+
await Promise.resolve();
|
|
114
|
+
const languageModel = this.getLanguageModel(provider, model.model);
|
|
115
|
+
const call = buildBaseTextCallOptions({
|
|
116
|
+
languageModel,
|
|
117
|
+
request,
|
|
118
|
+
defaultProviderMaxRetries: this.defaultProviderMaxRetries
|
|
119
|
+
});
|
|
120
|
+
const result = ai.streamText(call);
|
|
121
|
+
return {
|
|
122
|
+
textStream: result.textStream,
|
|
123
|
+
consumeStream: async () => {
|
|
124
|
+
await result.consumeStream();
|
|
125
|
+
},
|
|
126
|
+
finishReason: Promise.resolve(result.finishReason).then(
|
|
127
|
+
(value) => value ?? null
|
|
128
|
+
),
|
|
129
|
+
usage: Promise.resolve(result.totalUsage).then(
|
|
130
|
+
(value) => normalizeUsage(value)
|
|
131
|
+
),
|
|
132
|
+
warnings: Promise.resolve(result.warnings).then(
|
|
133
|
+
(value) => normalizeWarnings(value)
|
|
134
|
+
),
|
|
135
|
+
raw: result
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
getLanguageModel(provider, modelId) {
|
|
139
|
+
const handle = this.providerCache.get(provider.name) ?? createProviderHandle(provider);
|
|
140
|
+
if (!this.providerCache.has(provider.name)) {
|
|
141
|
+
this.providerCache.set(provider.name, handle);
|
|
142
|
+
}
|
|
143
|
+
return resolveLanguageModel(handle, modelId, provider.name);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
function buildBaseTextCallOptions({
|
|
147
|
+
languageModel,
|
|
148
|
+
request,
|
|
149
|
+
defaultProviderMaxRetries
|
|
150
|
+
}) {
|
|
151
|
+
const call = {
|
|
152
|
+
model: languageModel,
|
|
153
|
+
system: request.system,
|
|
154
|
+
temperature: request.temperature,
|
|
155
|
+
topP: request.topP,
|
|
156
|
+
maxRetries: request.providerMaxRetries ?? defaultProviderMaxRetries,
|
|
157
|
+
abortSignal: request.abortSignal
|
|
158
|
+
};
|
|
159
|
+
if (request.maxOutputTokens !== void 0) {
|
|
160
|
+
call.maxOutputTokens = request.maxOutputTokens;
|
|
161
|
+
}
|
|
162
|
+
if (request.stopSequences !== void 0) {
|
|
163
|
+
call.stopSequences = request.stopSequences;
|
|
164
|
+
}
|
|
165
|
+
if ("prompt" in request) {
|
|
166
|
+
call.prompt = request.prompt;
|
|
167
|
+
} else {
|
|
168
|
+
call.messages = request.messages;
|
|
169
|
+
}
|
|
170
|
+
return call;
|
|
171
|
+
}
|
|
172
|
+
function createProviderHandle(provider) {
|
|
173
|
+
const apiKey = provider.auth.apiKey.trim();
|
|
174
|
+
if (!apiKey) {
|
|
175
|
+
throw new RouterConfigurationError(
|
|
176
|
+
`Provider "${provider.name}" is missing an API key.`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
switch (provider.type) {
|
|
180
|
+
case "anthropic": {
|
|
181
|
+
const options = { apiKey };
|
|
182
|
+
if (provider.baseURL) {
|
|
183
|
+
options.baseURL = provider.baseURL;
|
|
184
|
+
}
|
|
185
|
+
if (provider.headers) {
|
|
186
|
+
options.headers = provider.headers;
|
|
187
|
+
}
|
|
188
|
+
return anthropic.createAnthropic(options);
|
|
189
|
+
}
|
|
190
|
+
case "cohere": {
|
|
191
|
+
const options = { apiKey };
|
|
192
|
+
if (provider.baseURL) {
|
|
193
|
+
options.baseURL = provider.baseURL;
|
|
194
|
+
}
|
|
195
|
+
if (provider.headers) {
|
|
196
|
+
options.headers = provider.headers;
|
|
197
|
+
}
|
|
198
|
+
return cohere.createCohere(options);
|
|
199
|
+
}
|
|
200
|
+
case "deepseek": {
|
|
201
|
+
const options = { apiKey };
|
|
202
|
+
if (provider.baseURL) {
|
|
203
|
+
options.baseURL = provider.baseURL;
|
|
204
|
+
}
|
|
205
|
+
if (provider.headers) {
|
|
206
|
+
options.headers = provider.headers;
|
|
207
|
+
}
|
|
208
|
+
return deepseek.createDeepSeek(options);
|
|
209
|
+
}
|
|
210
|
+
case "google": {
|
|
211
|
+
const options = {
|
|
212
|
+
apiKey
|
|
213
|
+
};
|
|
214
|
+
if (provider.baseURL) {
|
|
215
|
+
options.baseURL = provider.baseURL;
|
|
216
|
+
}
|
|
217
|
+
if (provider.headers) {
|
|
218
|
+
options.headers = provider.headers;
|
|
219
|
+
}
|
|
220
|
+
return google.createGoogleGenerativeAI(options);
|
|
221
|
+
}
|
|
222
|
+
case "groq": {
|
|
223
|
+
const options = { apiKey };
|
|
224
|
+
if (provider.baseURL) {
|
|
225
|
+
options.baseURL = provider.baseURL;
|
|
226
|
+
}
|
|
227
|
+
if (provider.headers) {
|
|
228
|
+
options.headers = provider.headers;
|
|
229
|
+
}
|
|
230
|
+
return groq.createGroq(options);
|
|
231
|
+
}
|
|
232
|
+
case "mistral": {
|
|
233
|
+
const options = { apiKey };
|
|
234
|
+
if (provider.baseURL) {
|
|
235
|
+
options.baseURL = provider.baseURL;
|
|
236
|
+
}
|
|
237
|
+
if (provider.headers) {
|
|
238
|
+
options.headers = provider.headers;
|
|
239
|
+
}
|
|
240
|
+
return mistral.createMistral(options);
|
|
241
|
+
}
|
|
242
|
+
case "openai": {
|
|
243
|
+
const options = { apiKey };
|
|
244
|
+
if (provider.baseURL) {
|
|
245
|
+
options.baseURL = provider.baseURL;
|
|
246
|
+
}
|
|
247
|
+
if (provider.headers) {
|
|
248
|
+
options.headers = provider.headers;
|
|
249
|
+
}
|
|
250
|
+
return openai.createOpenAI(options);
|
|
251
|
+
}
|
|
252
|
+
case "openai-compatible": {
|
|
253
|
+
const options = {
|
|
254
|
+
name: provider.providerLabel ?? provider.name,
|
|
255
|
+
apiKey,
|
|
256
|
+
baseURL: provider.baseURL
|
|
257
|
+
};
|
|
258
|
+
if (provider.headers) {
|
|
259
|
+
options.headers = provider.headers;
|
|
260
|
+
}
|
|
261
|
+
if (provider.queryParams) {
|
|
262
|
+
options.queryParams = provider.queryParams;
|
|
263
|
+
}
|
|
264
|
+
return openaiCompatible.createOpenAICompatible(options);
|
|
265
|
+
}
|
|
266
|
+
case "openrouter": {
|
|
267
|
+
const options = {
|
|
268
|
+
apiKey
|
|
269
|
+
};
|
|
270
|
+
const headers = buildOpenRouterHeaders(provider);
|
|
271
|
+
if (headers) {
|
|
272
|
+
options.headers = headers;
|
|
273
|
+
}
|
|
274
|
+
if (provider.baseURL) {
|
|
275
|
+
options.baseURL = provider.baseURL;
|
|
276
|
+
}
|
|
277
|
+
return aiSdkProvider.createOpenRouter(options);
|
|
278
|
+
}
|
|
279
|
+
case "perplexity": {
|
|
280
|
+
const options = { apiKey };
|
|
281
|
+
if (provider.baseURL) {
|
|
282
|
+
options.baseURL = provider.baseURL;
|
|
283
|
+
}
|
|
284
|
+
if (provider.headers) {
|
|
285
|
+
options.headers = provider.headers;
|
|
286
|
+
}
|
|
287
|
+
return perplexity.createPerplexity(options);
|
|
288
|
+
}
|
|
289
|
+
case "togetherai": {
|
|
290
|
+
const options = { apiKey };
|
|
291
|
+
if (provider.baseURL) {
|
|
292
|
+
options.baseURL = provider.baseURL;
|
|
293
|
+
}
|
|
294
|
+
if (provider.headers) {
|
|
295
|
+
options.headers = provider.headers;
|
|
296
|
+
}
|
|
297
|
+
return togetherai.createTogetherAI(options);
|
|
298
|
+
}
|
|
299
|
+
case "xai": {
|
|
300
|
+
const options = { apiKey };
|
|
301
|
+
if (provider.baseURL) {
|
|
302
|
+
options.baseURL = provider.baseURL;
|
|
303
|
+
}
|
|
304
|
+
if (provider.headers) {
|
|
305
|
+
options.headers = provider.headers;
|
|
306
|
+
}
|
|
307
|
+
return xai.createXai(options);
|
|
308
|
+
}
|
|
309
|
+
default: {
|
|
310
|
+
const exhaustiveCheck = provider;
|
|
311
|
+
throw new RouterConfigurationError(
|
|
312
|
+
`Unsupported provider type: ${JSON.stringify(exhaustiveCheck)}`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function buildOpenRouterHeaders(provider) {
|
|
318
|
+
const headers = { ...provider.headers };
|
|
319
|
+
if (provider.appUrl) {
|
|
320
|
+
headers["HTTP-Referer"] = provider.appUrl;
|
|
321
|
+
}
|
|
322
|
+
if (provider.appName) {
|
|
323
|
+
headers["X-Title"] = provider.appName;
|
|
324
|
+
}
|
|
325
|
+
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
326
|
+
}
|
|
327
|
+
function resolveLanguageModel(providerHandle, modelId, providerName) {
|
|
328
|
+
if (typeof providerHandle === "function") {
|
|
329
|
+
return providerHandle(modelId);
|
|
330
|
+
}
|
|
331
|
+
const dynamicHandle = providerHandle;
|
|
332
|
+
const candidates = ["languageModel", "chatModel", "chat"];
|
|
333
|
+
for (const candidate of candidates) {
|
|
334
|
+
const factory = dynamicHandle[candidate];
|
|
335
|
+
if (typeof factory === "function") {
|
|
336
|
+
return factory(modelId);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
throw new RouterConfigurationError(
|
|
340
|
+
`Provider "${providerName}" does not expose a supported language model factory.`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
function normalizeUsage(usage) {
|
|
344
|
+
if (!usage || typeof usage !== "object") {
|
|
345
|
+
return void 0;
|
|
346
|
+
}
|
|
347
|
+
const numericUsage = usage;
|
|
348
|
+
const normalized = {};
|
|
349
|
+
const keys = [
|
|
350
|
+
"inputTokens",
|
|
351
|
+
"outputTokens",
|
|
352
|
+
"totalTokens",
|
|
353
|
+
"reasoningTokens",
|
|
354
|
+
"cachedInputTokens"
|
|
355
|
+
];
|
|
356
|
+
for (const key of keys) {
|
|
357
|
+
const value = numericUsage[key];
|
|
358
|
+
if (typeof value === "number") {
|
|
359
|
+
normalized[key] = value;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
363
|
+
}
|
|
364
|
+
function normalizeWarnings(warnings) {
|
|
365
|
+
return Array.isArray(warnings) ? warnings : void 0;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/source-builders.ts
|
|
369
|
+
function createLlmConnection(provider) {
|
|
370
|
+
assertConnectionProviderName(provider);
|
|
371
|
+
return {
|
|
372
|
+
provider
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function createLlmSource(connection, config) {
|
|
376
|
+
assertSourceConfig(config);
|
|
377
|
+
const normalizedConfig = config.access === "free" ? config : {
|
|
378
|
+
...config,
|
|
379
|
+
access: "standard"
|
|
380
|
+
};
|
|
381
|
+
return {
|
|
382
|
+
connection,
|
|
383
|
+
config: normalizedConfig
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function assertConnectionProviderName(provider) {
|
|
387
|
+
if (!provider.name.trim()) {
|
|
388
|
+
throw new RouterConfigurationError(
|
|
389
|
+
"Connection provider names must be non-empty."
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function assertSourceConfig(config) {
|
|
394
|
+
if (!config.name.trim()) {
|
|
395
|
+
throw new RouterConfigurationError("Source names must be non-empty.");
|
|
396
|
+
}
|
|
397
|
+
if (!config.model.trim()) {
|
|
398
|
+
throw new RouterConfigurationError("Source models must be non-empty.");
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/prio-llm-router.ts
|
|
403
|
+
var PrioLlmRouter = class {
|
|
404
|
+
providersByName = /* @__PURE__ */ new Map();
|
|
405
|
+
modelsByName = /* @__PURE__ */ new Map();
|
|
406
|
+
defaultChain;
|
|
407
|
+
executor;
|
|
408
|
+
hooks;
|
|
409
|
+
constructor(options) {
|
|
410
|
+
const normalized = resolveRouterConfig(options);
|
|
411
|
+
if (normalized.providers.length === 0) {
|
|
412
|
+
throw new RouterConfigurationError(
|
|
413
|
+
"At least one provider configuration is required."
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
if (normalized.models.length === 0) {
|
|
417
|
+
throw new RouterConfigurationError(
|
|
418
|
+
"At least one model configuration is required."
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
this.defaultChain = normalized.defaultChain;
|
|
422
|
+
this.hooks = options.hooks;
|
|
423
|
+
this.executor = options.executor ?? (options.defaultProviderMaxRetries === void 0 ? createDefaultTextGenerationExecutor() : createDefaultTextGenerationExecutor({
|
|
424
|
+
defaultProviderMaxRetries: options.defaultProviderMaxRetries
|
|
425
|
+
}));
|
|
426
|
+
for (const provider of normalized.providers) {
|
|
427
|
+
this.assertUniqueName(
|
|
428
|
+
this.providersByName,
|
|
429
|
+
provider.name,
|
|
430
|
+
"provider configuration"
|
|
431
|
+
);
|
|
432
|
+
this.validateProvider(provider);
|
|
433
|
+
this.providersByName.set(provider.name, provider);
|
|
434
|
+
}
|
|
435
|
+
normalized.models.forEach((model, index) => {
|
|
436
|
+
this.assertUniqueName(this.modelsByName, model.name, "model configuration");
|
|
437
|
+
if (!this.providersByName.has(model.provider)) {
|
|
438
|
+
throw new RouterConfigurationError(
|
|
439
|
+
`Model "${model.name}" references unknown provider "${model.provider}".`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
this.modelsByName.set(model.name, {
|
|
443
|
+
...model,
|
|
444
|
+
__index: index
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
if (this.defaultChain) {
|
|
448
|
+
this.resolveNamedChain(this.defaultChain);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
listProviders() {
|
|
452
|
+
return [...this.providersByName.values()];
|
|
453
|
+
}
|
|
454
|
+
listModels() {
|
|
455
|
+
return [...this.modelsByName.values()].sort(compareModels).map((model) => this.toResolvedTarget(model));
|
|
456
|
+
}
|
|
457
|
+
async generateText(request) {
|
|
458
|
+
const chain = this.resolveExecutionChain(request.chain);
|
|
459
|
+
const attempts = [];
|
|
460
|
+
let lastError;
|
|
461
|
+
for (const [index, model] of chain.entries()) {
|
|
462
|
+
const provider = this.providersByName.get(model.provider);
|
|
463
|
+
if (!provider) {
|
|
464
|
+
throw new RouterConfigurationError(
|
|
465
|
+
`Provider "${model.provider}" was not found for target "${model.name}".`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
const pendingAttempt = {
|
|
469
|
+
attemptIndex: index,
|
|
470
|
+
targetName: model.name,
|
|
471
|
+
providerName: provider.name,
|
|
472
|
+
providerType: provider.type,
|
|
473
|
+
model: model.model,
|
|
474
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
475
|
+
};
|
|
476
|
+
if (model.tier !== void 0) {
|
|
477
|
+
pendingAttempt.tier = model.tier;
|
|
478
|
+
}
|
|
479
|
+
this.hooks?.onAttemptStart?.(pendingAttempt);
|
|
480
|
+
try {
|
|
481
|
+
const result = await this.executor.execute({
|
|
482
|
+
provider,
|
|
483
|
+
model,
|
|
484
|
+
request
|
|
485
|
+
});
|
|
486
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
487
|
+
const attemptRecord = {
|
|
488
|
+
...pendingAttempt,
|
|
489
|
+
finishedAt,
|
|
490
|
+
durationMs: finishedAt.getTime() - pendingAttempt.startedAt.getTime(),
|
|
491
|
+
success: true
|
|
492
|
+
};
|
|
493
|
+
attempts.push(attemptRecord);
|
|
494
|
+
this.hooks?.onAttemptSuccess?.(attemptRecord);
|
|
495
|
+
const response = {
|
|
496
|
+
text: result.text,
|
|
497
|
+
target: this.toResolvedTarget(model),
|
|
498
|
+
attempts,
|
|
499
|
+
finishReason: result.finishReason,
|
|
500
|
+
raw: result.raw
|
|
501
|
+
};
|
|
502
|
+
if (result.usage) {
|
|
503
|
+
response.usage = result.usage;
|
|
504
|
+
}
|
|
505
|
+
if (result.warnings) {
|
|
506
|
+
response.warnings = result.warnings;
|
|
507
|
+
}
|
|
508
|
+
return response;
|
|
509
|
+
} catch (error) {
|
|
510
|
+
if (isAbortError(error)) {
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
514
|
+
const attemptRecord = {
|
|
515
|
+
...pendingAttempt,
|
|
516
|
+
finishedAt,
|
|
517
|
+
durationMs: finishedAt.getTime() - pendingAttempt.startedAt.getTime(),
|
|
518
|
+
success: false,
|
|
519
|
+
error: serializeError(error)
|
|
520
|
+
};
|
|
521
|
+
attempts.push(attemptRecord);
|
|
522
|
+
this.hooks?.onAttemptFailure?.(attemptRecord);
|
|
523
|
+
lastError = error;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
throw new AllModelsFailedError(attempts, lastError);
|
|
527
|
+
}
|
|
528
|
+
async streamText(request) {
|
|
529
|
+
const chain = this.resolveExecutionChain(request.chain);
|
|
530
|
+
const attempts = [];
|
|
531
|
+
let lastError;
|
|
532
|
+
for (const [index, model] of chain.entries()) {
|
|
533
|
+
const provider = this.providersByName.get(model.provider);
|
|
534
|
+
if (!provider) {
|
|
535
|
+
throw new RouterConfigurationError(
|
|
536
|
+
`Provider "${model.provider}" was not found for target "${model.name}".`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
const pendingAttempt = createPendingAttempt(index, provider, model);
|
|
540
|
+
this.hooks?.onAttemptStart?.(pendingAttempt);
|
|
541
|
+
const { controller, cleanup, parentAborted } = createLinkedAbortController(
|
|
542
|
+
request.abortSignal
|
|
543
|
+
);
|
|
544
|
+
try {
|
|
545
|
+
const streamResult = await this.executor.stream({
|
|
546
|
+
provider,
|
|
547
|
+
model,
|
|
548
|
+
request: {
|
|
549
|
+
...request,
|
|
550
|
+
abortSignal: controller.signal
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
const iterator = streamResult.textStream[Symbol.asyncIterator]();
|
|
554
|
+
const firstChunk = await this.waitForFirstChunk({
|
|
555
|
+
iterator,
|
|
556
|
+
timeoutMs: request.firstChunkTimeoutMs,
|
|
557
|
+
abortController: controller,
|
|
558
|
+
parentAborted
|
|
559
|
+
});
|
|
560
|
+
if (firstChunk.done) {
|
|
561
|
+
throw createEmptyFirstChunkError(model.name);
|
|
562
|
+
}
|
|
563
|
+
return this.createStreamingResult({
|
|
564
|
+
pendingAttempt,
|
|
565
|
+
attempts,
|
|
566
|
+
model,
|
|
567
|
+
iterator,
|
|
568
|
+
streamResult,
|
|
569
|
+
firstChunk: firstChunk.value,
|
|
570
|
+
cleanupAbortLink: cleanup
|
|
571
|
+
});
|
|
572
|
+
} catch (error) {
|
|
573
|
+
cleanup();
|
|
574
|
+
if (isAbortError(error) && parentAborted()) {
|
|
575
|
+
throw error;
|
|
576
|
+
}
|
|
577
|
+
const attemptRecord = createFailedAttemptRecord(pendingAttempt, error);
|
|
578
|
+
attempts.push(attemptRecord);
|
|
579
|
+
this.hooks?.onAttemptFailure?.(attemptRecord);
|
|
580
|
+
lastError = error;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
throw new AllModelsFailedError(attempts, lastError);
|
|
584
|
+
}
|
|
585
|
+
createStreamingResult(options) {
|
|
586
|
+
const {
|
|
587
|
+
pendingAttempt,
|
|
588
|
+
attempts,
|
|
589
|
+
model,
|
|
590
|
+
iterator,
|
|
591
|
+
streamResult,
|
|
592
|
+
firstChunk,
|
|
593
|
+
cleanupAbortLink
|
|
594
|
+
} = options;
|
|
595
|
+
let started = false;
|
|
596
|
+
const textParts = [firstChunk];
|
|
597
|
+
let resolveFinal;
|
|
598
|
+
let rejectFinal;
|
|
599
|
+
let finalized = false;
|
|
600
|
+
const final = new Promise((resolve, reject) => {
|
|
601
|
+
resolveFinal = resolve;
|
|
602
|
+
rejectFinal = reject;
|
|
603
|
+
});
|
|
604
|
+
const finalizeSuccess = async () => {
|
|
605
|
+
if (finalized) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
finalized = true;
|
|
609
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
610
|
+
const attemptRecord = {
|
|
611
|
+
...pendingAttempt,
|
|
612
|
+
finishedAt,
|
|
613
|
+
durationMs: finishedAt.getTime() - pendingAttempt.startedAt.getTime(),
|
|
614
|
+
success: true
|
|
615
|
+
};
|
|
616
|
+
attempts.push(attemptRecord);
|
|
617
|
+
this.hooks?.onAttemptSuccess?.(attemptRecord);
|
|
618
|
+
const result = {
|
|
619
|
+
text: textParts.join(""),
|
|
620
|
+
target: this.toResolvedTarget(model),
|
|
621
|
+
attempts: [...attempts],
|
|
622
|
+
finishReason: await streamResult.finishReason,
|
|
623
|
+
raw: streamResult.raw
|
|
624
|
+
};
|
|
625
|
+
const usage = await streamResult.usage;
|
|
626
|
+
if (usage) {
|
|
627
|
+
result.usage = usage;
|
|
628
|
+
}
|
|
629
|
+
const warnings = await streamResult.warnings;
|
|
630
|
+
if (warnings) {
|
|
631
|
+
result.warnings = warnings;
|
|
632
|
+
}
|
|
633
|
+
cleanupAbortLink();
|
|
634
|
+
resolveFinal(result);
|
|
635
|
+
};
|
|
636
|
+
const finalizeFailure = (error) => {
|
|
637
|
+
if (finalized) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
finalized = true;
|
|
641
|
+
const attemptRecord = createFailedAttemptRecord(pendingAttempt, error);
|
|
642
|
+
attempts.push(attemptRecord);
|
|
643
|
+
this.hooks?.onAttemptFailure?.(attemptRecord);
|
|
644
|
+
cleanupAbortLink();
|
|
645
|
+
rejectFinal(error);
|
|
646
|
+
};
|
|
647
|
+
const wrappedStream = {
|
|
648
|
+
[Symbol.asyncIterator]: () => {
|
|
649
|
+
if (started) {
|
|
650
|
+
throw new RouterConfigurationError(
|
|
651
|
+
"This stream can only be consumed once."
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
started = true;
|
|
655
|
+
return createRouterTextStreamIterator({
|
|
656
|
+
firstChunk,
|
|
657
|
+
iterator,
|
|
658
|
+
onChunk: (chunk) => {
|
|
659
|
+
textParts.push(chunk);
|
|
660
|
+
},
|
|
661
|
+
onSuccess: finalizeSuccess,
|
|
662
|
+
onFailure: finalizeFailure
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
return {
|
|
667
|
+
target: this.toResolvedTarget(model),
|
|
668
|
+
selectedAttempt: pendingAttempt,
|
|
669
|
+
attempts: [...attempts],
|
|
670
|
+
textStream: wrappedStream,
|
|
671
|
+
final,
|
|
672
|
+
consumeStream: async () => {
|
|
673
|
+
if (!started) {
|
|
674
|
+
for await (const _ of wrappedStream) {
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return final;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
async waitForFirstChunk(options) {
|
|
682
|
+
const { iterator, timeoutMs, abortController, parentAborted } = options;
|
|
683
|
+
const nextPromise = iterator.next();
|
|
684
|
+
if (timeoutMs === void 0) {
|
|
685
|
+
return nextPromise;
|
|
686
|
+
}
|
|
687
|
+
const timeoutError = createFirstChunkTimeoutError(timeoutMs);
|
|
688
|
+
const timedRace = await Promise.race([
|
|
689
|
+
nextPromise.then(
|
|
690
|
+
(value) => ({ kind: "value", value }),
|
|
691
|
+
(error) => ({ kind: "error", error })
|
|
692
|
+
),
|
|
693
|
+
delay(timeoutMs).then(() => ({ kind: "timeout" }))
|
|
694
|
+
]);
|
|
695
|
+
if (timedRace.kind === "value") {
|
|
696
|
+
return timedRace.value;
|
|
697
|
+
}
|
|
698
|
+
if (timedRace.kind === "timeout") {
|
|
699
|
+
abortController.abort(timeoutError);
|
|
700
|
+
void nextPromise.catch(() => void 0);
|
|
701
|
+
throw timeoutError;
|
|
702
|
+
}
|
|
703
|
+
if (isAbortError(timedRace.error) && parentAborted()) {
|
|
704
|
+
throw timedRace.error;
|
|
705
|
+
}
|
|
706
|
+
throw timedRace.error;
|
|
707
|
+
}
|
|
708
|
+
resolveExecutionChain(chain) {
|
|
709
|
+
if (chain?.length) {
|
|
710
|
+
return this.resolveNamedChain(chain);
|
|
711
|
+
}
|
|
712
|
+
if (this.defaultChain?.length) {
|
|
713
|
+
return this.resolveNamedChain(this.defaultChain);
|
|
714
|
+
}
|
|
715
|
+
const implicitChain = [...this.modelsByName.values()].filter((model) => this.isModelEnabled(model)).sort(compareModels);
|
|
716
|
+
if (implicitChain.length === 0) {
|
|
717
|
+
throw new RouterConfigurationError(
|
|
718
|
+
"No enabled model targets are available for execution."
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
return implicitChain;
|
|
722
|
+
}
|
|
723
|
+
resolveNamedChain(chain) {
|
|
724
|
+
const resolved = [];
|
|
725
|
+
const seen = /* @__PURE__ */ new Set();
|
|
726
|
+
for (const targetName of chain) {
|
|
727
|
+
if (seen.has(targetName)) {
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
seen.add(targetName);
|
|
731
|
+
const model = this.modelsByName.get(targetName);
|
|
732
|
+
if (!model) {
|
|
733
|
+
throw new RouterConfigurationError(
|
|
734
|
+
`Model target "${targetName}" is not configured.`
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
if (!this.isModelEnabled(model)) {
|
|
738
|
+
throw new RouterConfigurationError(
|
|
739
|
+
`Model target "${targetName}" is disabled or its provider is disabled.`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
resolved.push(model);
|
|
743
|
+
}
|
|
744
|
+
if (resolved.length === 0) {
|
|
745
|
+
throw new RouterConfigurationError(
|
|
746
|
+
"The resolved execution chain is empty."
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
return resolved;
|
|
750
|
+
}
|
|
751
|
+
isModelEnabled(model) {
|
|
752
|
+
if (model.enabled === false) {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
const provider = this.providersByName.get(model.provider);
|
|
756
|
+
return provider?.enabled !== false;
|
|
757
|
+
}
|
|
758
|
+
toResolvedTarget(model) {
|
|
759
|
+
const provider = this.providersByName.get(model.provider);
|
|
760
|
+
if (!provider) {
|
|
761
|
+
throw new RouterConfigurationError(
|
|
762
|
+
`Model "${model.name}" references missing provider "${model.provider}".`
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
const resolvedTarget = {
|
|
766
|
+
name: model.name,
|
|
767
|
+
providerName: provider.name,
|
|
768
|
+
providerType: provider.type,
|
|
769
|
+
model: model.model
|
|
770
|
+
};
|
|
771
|
+
if (model.priority !== void 0) {
|
|
772
|
+
resolvedTarget.priority = model.priority;
|
|
773
|
+
}
|
|
774
|
+
if (model.tier !== void 0) {
|
|
775
|
+
resolvedTarget.tier = model.tier;
|
|
776
|
+
}
|
|
777
|
+
if (model.metadata !== void 0) {
|
|
778
|
+
resolvedTarget.metadata = model.metadata;
|
|
779
|
+
}
|
|
780
|
+
return resolvedTarget;
|
|
781
|
+
}
|
|
782
|
+
validateProvider(provider) {
|
|
783
|
+
if (!provider.name.trim()) {
|
|
784
|
+
throw new RouterConfigurationError(
|
|
785
|
+
"Provider configuration names must be non-empty."
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
if (!provider.auth.apiKey.trim()) {
|
|
789
|
+
throw new RouterConfigurationError(
|
|
790
|
+
`Provider "${provider.name}" requires a non-empty API key.`
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
assertUniqueName(registry, name, label) {
|
|
795
|
+
if (!name.trim()) {
|
|
796
|
+
throw new RouterConfigurationError(`${label} names must be non-empty.`);
|
|
797
|
+
}
|
|
798
|
+
if (registry.has(name)) {
|
|
799
|
+
throw new RouterConfigurationError(
|
|
800
|
+
`Duplicate ${label} name "${name}" detected.`
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
function createLlmRouter(options) {
|
|
806
|
+
return new PrioLlmRouter(options);
|
|
807
|
+
}
|
|
808
|
+
function resolveRouterConfig(options) {
|
|
809
|
+
if ("sources" in options) {
|
|
810
|
+
return compileSources(options.sources, options.defaultChain);
|
|
811
|
+
}
|
|
812
|
+
const normalized = {
|
|
813
|
+
providers: options.providers,
|
|
814
|
+
models: options.models
|
|
815
|
+
};
|
|
816
|
+
if (options.defaultChain !== void 0) {
|
|
817
|
+
normalized.defaultChain = options.defaultChain;
|
|
818
|
+
}
|
|
819
|
+
return normalized;
|
|
820
|
+
}
|
|
821
|
+
function compareModels(left, right) {
|
|
822
|
+
const leftPriority = left.priority ?? Number.MAX_SAFE_INTEGER;
|
|
823
|
+
const rightPriority = right.priority ?? Number.MAX_SAFE_INTEGER;
|
|
824
|
+
if (leftPriority !== rightPriority) {
|
|
825
|
+
return leftPriority - rightPriority;
|
|
826
|
+
}
|
|
827
|
+
return left.__index - right.__index;
|
|
828
|
+
}
|
|
829
|
+
function compileSources(sources, defaultChain) {
|
|
830
|
+
const providersByName = /* @__PURE__ */ new Map();
|
|
831
|
+
const models = [];
|
|
832
|
+
for (const source of sources) {
|
|
833
|
+
const provider = source.connection.provider;
|
|
834
|
+
const providerName = provider.name.trim();
|
|
835
|
+
const modelId = source.config.model.trim();
|
|
836
|
+
const access = source.config.access ?? "standard";
|
|
837
|
+
if (!providerName) {
|
|
838
|
+
throw new RouterConfigurationError(
|
|
839
|
+
"Connection provider names must be non-empty."
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
if (!source.config.name.trim()) {
|
|
843
|
+
throw new RouterConfigurationError("Source names must be non-empty.");
|
|
844
|
+
}
|
|
845
|
+
if (!modelId) {
|
|
846
|
+
throw new RouterConfigurationError("Source models must be non-empty.");
|
|
847
|
+
}
|
|
848
|
+
const existingProvider = providersByName.get(providerName);
|
|
849
|
+
if (existingProvider) {
|
|
850
|
+
assertMatchingSourceProvider(existingProvider, provider);
|
|
851
|
+
} else {
|
|
852
|
+
providersByName.set(providerName, provider);
|
|
853
|
+
}
|
|
854
|
+
if (access === "free") {
|
|
855
|
+
assertGuaranteedFreeSource(provider, source.config.model);
|
|
856
|
+
}
|
|
857
|
+
if (access === "free" && source.config.tier === "paid") {
|
|
858
|
+
throw new RouterConfigurationError(
|
|
859
|
+
`Free source "${source.config.name}" cannot be marked as paid.`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
const compiledModel = {
|
|
863
|
+
name: source.config.name,
|
|
864
|
+
provider: providerName,
|
|
865
|
+
model: modelId
|
|
866
|
+
};
|
|
867
|
+
if (source.config.enabled !== void 0) {
|
|
868
|
+
compiledModel.enabled = source.config.enabled;
|
|
869
|
+
}
|
|
870
|
+
if (source.config.priority !== void 0) {
|
|
871
|
+
compiledModel.priority = source.config.priority;
|
|
872
|
+
}
|
|
873
|
+
const tier = source.config.tier ?? (access === "free" ? "free" : void 0);
|
|
874
|
+
if (tier !== void 0) {
|
|
875
|
+
compiledModel.tier = tier;
|
|
876
|
+
}
|
|
877
|
+
if (source.config.metadata !== void 0) {
|
|
878
|
+
compiledModel.metadata = source.config.metadata;
|
|
879
|
+
}
|
|
880
|
+
models.push(compiledModel);
|
|
881
|
+
}
|
|
882
|
+
const normalized = {
|
|
883
|
+
providers: [...providersByName.values()],
|
|
884
|
+
models
|
|
885
|
+
};
|
|
886
|
+
if (defaultChain !== void 0) {
|
|
887
|
+
normalized.defaultChain = defaultChain;
|
|
888
|
+
}
|
|
889
|
+
return normalized;
|
|
890
|
+
}
|
|
891
|
+
function assertMatchingSourceProvider(existingProvider, nextProvider) {
|
|
892
|
+
if (JSON.stringify(existingProvider) === JSON.stringify(nextProvider)) {
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
throw new RouterConfigurationError(
|
|
896
|
+
`Connection provider "${existingProvider.name}" is configured more than once with different settings.`
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
function assertGuaranteedFreeSource(provider, model) {
|
|
900
|
+
if (provider.type !== "openrouter") {
|
|
901
|
+
throw new RouterConfigurationError(
|
|
902
|
+
`Provider "${provider.name}" does not support strict free sources. Only OpenRouter with explicit ":free" model variants is supported today.`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
const normalizedModel = model.trim();
|
|
906
|
+
if (normalizedModel === "openrouter/free") {
|
|
907
|
+
throw new RouterConfigurationError(
|
|
908
|
+
`Free source "${provider.name}" cannot use "openrouter/free". Use an explicit ":free" model id instead.`
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
if (!normalizedModel.endsWith(":free")) {
|
|
912
|
+
throw new RouterConfigurationError(
|
|
913
|
+
`Free OpenRouter sources must use an explicit ":free" model id. Received "${normalizedModel}".`
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function createPendingAttempt(attemptIndex, provider, model) {
|
|
918
|
+
const pendingAttempt = {
|
|
919
|
+
attemptIndex,
|
|
920
|
+
targetName: model.name,
|
|
921
|
+
providerName: provider.name,
|
|
922
|
+
providerType: provider.type,
|
|
923
|
+
model: model.model,
|
|
924
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
925
|
+
};
|
|
926
|
+
if (model.tier !== void 0) {
|
|
927
|
+
pendingAttempt.tier = model.tier;
|
|
928
|
+
}
|
|
929
|
+
return pendingAttempt;
|
|
930
|
+
}
|
|
931
|
+
function createFailedAttemptRecord(pendingAttempt, error) {
|
|
932
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
933
|
+
return {
|
|
934
|
+
...pendingAttempt,
|
|
935
|
+
finishedAt,
|
|
936
|
+
durationMs: finishedAt.getTime() - pendingAttempt.startedAt.getTime(),
|
|
937
|
+
success: false,
|
|
938
|
+
error: serializeError(error)
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
function createRouterTextStreamIterator(options) {
|
|
942
|
+
const { firstChunk, iterator, onChunk, onSuccess, onFailure } = options;
|
|
943
|
+
let firstYielded = false;
|
|
944
|
+
let finished = false;
|
|
945
|
+
return {
|
|
946
|
+
async next() {
|
|
947
|
+
if (finished) {
|
|
948
|
+
return {
|
|
949
|
+
done: true,
|
|
950
|
+
value: void 0
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
if (!firstYielded) {
|
|
954
|
+
firstYielded = true;
|
|
955
|
+
return {
|
|
956
|
+
done: false,
|
|
957
|
+
value: firstChunk
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
try {
|
|
961
|
+
const next = await iterator.next();
|
|
962
|
+
if (next.done) {
|
|
963
|
+
finished = true;
|
|
964
|
+
await onSuccess();
|
|
965
|
+
return {
|
|
966
|
+
done: true,
|
|
967
|
+
value: void 0
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
onChunk(next.value);
|
|
971
|
+
return {
|
|
972
|
+
done: false,
|
|
973
|
+
value: next.value
|
|
974
|
+
};
|
|
975
|
+
} catch (error) {
|
|
976
|
+
finished = true;
|
|
977
|
+
onFailure(error);
|
|
978
|
+
throw error;
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
async return() {
|
|
982
|
+
finished = true;
|
|
983
|
+
onFailure(createStreamClosedEarlyError());
|
|
984
|
+
if (typeof iterator.return === "function") {
|
|
985
|
+
await iterator.return();
|
|
986
|
+
}
|
|
987
|
+
return {
|
|
988
|
+
done: true,
|
|
989
|
+
value: void 0
|
|
990
|
+
};
|
|
991
|
+
},
|
|
992
|
+
async throw(error) {
|
|
993
|
+
finished = true;
|
|
994
|
+
onFailure(error);
|
|
995
|
+
if (typeof iterator.throw === "function") {
|
|
996
|
+
return iterator.throw(error);
|
|
997
|
+
}
|
|
998
|
+
throw error;
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
function createLinkedAbortController(parentSignal) {
|
|
1003
|
+
const controller = new AbortController();
|
|
1004
|
+
let abortedByParent = false;
|
|
1005
|
+
const abortFromParent = () => {
|
|
1006
|
+
abortedByParent = true;
|
|
1007
|
+
controller.abort(parentSignal?.reason);
|
|
1008
|
+
};
|
|
1009
|
+
if (parentSignal?.aborted) {
|
|
1010
|
+
abortFromParent();
|
|
1011
|
+
} else {
|
|
1012
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
1013
|
+
}
|
|
1014
|
+
return {
|
|
1015
|
+
controller,
|
|
1016
|
+
cleanup: () => {
|
|
1017
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
1018
|
+
},
|
|
1019
|
+
parentAborted: () => abortedByParent
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
function createFirstChunkTimeoutError(timeoutMs) {
|
|
1023
|
+
const error = new Error(
|
|
1024
|
+
`The first stream chunk did not arrive within ${timeoutMs}ms.`
|
|
1025
|
+
);
|
|
1026
|
+
error.name = "FirstChunkTimeoutError";
|
|
1027
|
+
return error;
|
|
1028
|
+
}
|
|
1029
|
+
function createEmptyFirstChunkError(targetName) {
|
|
1030
|
+
const error = new Error(
|
|
1031
|
+
`Stream for target "${targetName}" completed before the first text chunk.`
|
|
1032
|
+
);
|
|
1033
|
+
error.name = "EmptyStreamError";
|
|
1034
|
+
return error;
|
|
1035
|
+
}
|
|
1036
|
+
function createStreamClosedEarlyError() {
|
|
1037
|
+
const error = new Error("The stream was closed before completion.");
|
|
1038
|
+
error.name = "StreamClosedError";
|
|
1039
|
+
return error;
|
|
1040
|
+
}
|
|
1041
|
+
function delay(ms) {
|
|
1042
|
+
return new Promise((resolve) => {
|
|
1043
|
+
setTimeout(resolve, ms);
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
exports.AllModelsFailedError = AllModelsFailedError;
|
|
1048
|
+
exports.PrioLlmRouter = PrioLlmRouter;
|
|
1049
|
+
exports.PrioLlmRouterError = PrioLlmRouterError;
|
|
1050
|
+
exports.RouterConfigurationError = RouterConfigurationError;
|
|
1051
|
+
exports.createDefaultTextGenerationExecutor = createDefaultTextGenerationExecutor;
|
|
1052
|
+
exports.createLlmConnection = createLlmConnection;
|
|
1053
|
+
exports.createLlmRouter = createLlmRouter;
|
|
1054
|
+
exports.createLlmSource = createLlmSource;
|
|
1055
|
+
exports.isAbortError = isAbortError;
|
|
1056
|
+
exports.serializeError = serializeError;
|
|
1057
|
+
//# sourceMappingURL=index.cjs.map
|
|
1058
|
+
//# sourceMappingURL=index.cjs.map
|