@absolutejs/ai 0.6.4 → 0.6.6
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/CHANGELOG.md +19 -0
- package/README.md +18 -0
- package/changelog.json +37 -0
- package/dist/ai/index.js +359 -188
- package/dist/ai/index.js.map +17 -16
- package/dist/ai/providers/anthropic.js +147 -104
- package/dist/ai/providers/anthropic.js.map +8 -8
- package/dist/ai/providers/gemini.js +146 -103
- package/dist/ai/providers/gemini.js.map +7 -7
- package/dist/ai/providers/ollama.js +61 -20
- package/dist/ai/providers/ollama.js.map +5 -5
- package/dist/ai/providers/openai.js +245 -151
- package/dist/ai/providers/openai.js.map +11 -10
- package/dist/ai/providers/openaiCompatible.js +245 -151
- package/dist/ai/providers/openaiCompatible.js.map +11 -10
- package/dist/ai/providers/openaiResponses.js +243 -149
- package/dist/ai/providers/openaiResponses.js.map +10 -9
- package/dist/ai/providers/openrouter.js +246 -152
- package/dist/ai/providers/openrouter.js.map +12 -11
- package/dist/src/ai/contextPolicy.d.ts +2 -0
- package/dist/src/ai/errors/providerError.d.ts +2 -0
- package/dist/src/ai/index.d.ts +3 -0
- package/dist/src/ai/inputCapacity.d.ts +4 -2
- package/dist/src/ai/prepareTextInput.d.ts +2 -0
- package/dist/src/ai/providerFallback.d.ts +27 -8
- package/dist/src/ai/providers/openaiSchema.d.ts +4 -0
- package/package.json +3 -3
package/dist/ai/index.js
CHANGED
|
@@ -266,16 +266,191 @@ var toolResultBlock = (toolUseId, output, content = toolOutputText(output)) => {
|
|
|
266
266
|
return images ? { content, images, tool_use_id: toolUseId, type: "tool_result" } : { content, tool_use_id: toolUseId, type: "tool_result" };
|
|
267
267
|
};
|
|
268
268
|
|
|
269
|
+
// src/ai/errors/providerError.ts
|
|
270
|
+
var PROVIDER_STATUS_PAGES = {
|
|
271
|
+
anthropic: "https://status.claude.com",
|
|
272
|
+
gemini: "https://status.cloud.google.com",
|
|
273
|
+
google: "https://status.cloud.google.com",
|
|
274
|
+
openai: "https://status.openai.com",
|
|
275
|
+
openrouter: "https://status.openrouter.ai"
|
|
276
|
+
};
|
|
277
|
+
var RETRYABLE_STATUSES = new Set([
|
|
278
|
+
408,
|
|
279
|
+
409,
|
|
280
|
+
425,
|
|
281
|
+
429,
|
|
282
|
+
500,
|
|
283
|
+
502,
|
|
284
|
+
503,
|
|
285
|
+
504,
|
|
286
|
+
529
|
|
287
|
+
]);
|
|
288
|
+
var CONNECTION_ERROR_PATTERNS = [
|
|
289
|
+
"econnreset",
|
|
290
|
+
"econnrefused",
|
|
291
|
+
"etimedout",
|
|
292
|
+
"enotfound",
|
|
293
|
+
"eai_again",
|
|
294
|
+
"socket hang up",
|
|
295
|
+
"fetch failed",
|
|
296
|
+
"network",
|
|
297
|
+
"terminated",
|
|
298
|
+
"timed out",
|
|
299
|
+
"timeout",
|
|
300
|
+
"and not retryable",
|
|
301
|
+
"no response body"
|
|
302
|
+
];
|
|
303
|
+
var isAbortError = (err) => err instanceof Error && (err.name === "AbortError" || /\babort(ed)?\b/i.test(err.message));
|
|
304
|
+
var providerStatusPage = (provider) => PROVIDER_STATUS_PAGES[provider] ?? null;
|
|
305
|
+
var BILLING_ERROR_TYPES = new Set([
|
|
306
|
+
"credit_balance_exhausted",
|
|
307
|
+
"insufficient_quota",
|
|
308
|
+
"billing_hard_limit_reached",
|
|
309
|
+
"billing_not_active",
|
|
310
|
+
"insufficient_credits"
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
class ProviderError extends Error {
|
|
314
|
+
provider;
|
|
315
|
+
status;
|
|
316
|
+
type;
|
|
317
|
+
retryable;
|
|
318
|
+
billingUnavailable;
|
|
319
|
+
notSent;
|
|
320
|
+
metadata;
|
|
321
|
+
statusPageUrl;
|
|
322
|
+
constructor(init) {
|
|
323
|
+
super(init.message, init.cause === undefined ? undefined : { cause: init.cause });
|
|
324
|
+
this.name = "ProviderError";
|
|
325
|
+
this.provider = init.provider;
|
|
326
|
+
this.status = init.status ?? null;
|
|
327
|
+
this.type = init.type ?? (typeof init.metadata?.code === "string" ? init.metadata.code : typeof init.metadata?.type === "string" ? init.metadata.type : null);
|
|
328
|
+
this.billingUnavailable = BILLING_ERROR_TYPES.has(this.type ?? "") || init.status === 402 || init.provider === "anthropic" && init.status === 400 && /credit balance is too low/i.test(String(init.metadata?.message ?? init.message));
|
|
329
|
+
this.notSent = init.notSent === true;
|
|
330
|
+
this.retryable = !this.billingUnavailable && init.retryable;
|
|
331
|
+
this.metadata = init.metadata;
|
|
332
|
+
this.statusPageUrl = providerStatusPage(init.provider);
|
|
333
|
+
}
|
|
334
|
+
static fromResponse(provider, status, body, type) {
|
|
335
|
+
let metadata;
|
|
336
|
+
try {
|
|
337
|
+
const parsed = JSON.parse(body);
|
|
338
|
+
const details = parsed?.error ?? parsed;
|
|
339
|
+
if (details && typeof details === "object" && !Array.isArray(details))
|
|
340
|
+
metadata = details;
|
|
341
|
+
} catch {}
|
|
342
|
+
return new ProviderError({
|
|
343
|
+
metadata,
|
|
344
|
+
message: `${capitalize(provider)} API error ${status}: ${body}`,
|
|
345
|
+
provider,
|
|
346
|
+
retryable: RETRYABLE_STATUSES.has(status),
|
|
347
|
+
status,
|
|
348
|
+
type: type ?? null
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
static from(err, provider) {
|
|
352
|
+
if (err instanceof ProviderError)
|
|
353
|
+
return err;
|
|
354
|
+
if (isProviderError(err))
|
|
355
|
+
return new ProviderError({
|
|
356
|
+
provider: err.provider,
|
|
357
|
+
message: err.message,
|
|
358
|
+
status: err.status,
|
|
359
|
+
type: err.type,
|
|
360
|
+
retryable: err.retryable,
|
|
361
|
+
notSent: err.notSent,
|
|
362
|
+
metadata: err.metadata,
|
|
363
|
+
cause: err
|
|
364
|
+
});
|
|
365
|
+
if (isAbortError(err))
|
|
366
|
+
throw err;
|
|
367
|
+
const rawMessage = err instanceof Error ? err.message : String(err ?? "");
|
|
368
|
+
const lower = rawMessage.toLowerCase();
|
|
369
|
+
const statusMatch = lower.match(/api error\s+(\d{3})/);
|
|
370
|
+
const status = statusMatch ? Number(statusMatch[1]) : null;
|
|
371
|
+
const retryable = status !== null && RETRYABLE_STATUSES.has(status) || status === null && CONNECTION_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
|
|
372
|
+
return new ProviderError({
|
|
373
|
+
cause: err,
|
|
374
|
+
message: rawMessage || `${capitalize(provider)} request failed`,
|
|
375
|
+
provider,
|
|
376
|
+
retryable,
|
|
377
|
+
status
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
382
|
+
var isProviderError = (error) => error instanceof Error && error.name === "ProviderError" && ("provider" in error) && typeof error.provider === "string" && ("status" in error) && (error.status === null || typeof error.status === "number") && ("retryable" in error) && typeof error.retryable === "boolean" && ("billingUnavailable" in error) && typeof error.billingUnavailable === "boolean" && ("notSent" in error) && typeof error.notSent === "boolean";
|
|
383
|
+
|
|
384
|
+
// src/ai/providerFallback.ts
|
|
385
|
+
var providerFallbackModels = (model, recovery) => [
|
|
386
|
+
...new Set([
|
|
387
|
+
model,
|
|
388
|
+
...recovery ? typeof recovery.models === "function" ? recovery.models(model) : recovery.models : []
|
|
389
|
+
])
|
|
390
|
+
];
|
|
391
|
+
var isAIProviderFailure = (error) => {
|
|
392
|
+
if (isProviderError(error))
|
|
393
|
+
return error.billingUnavailable || error.retryable || error.notSent || error.status !== null && [400, 401, 403, 404, 413, 422].includes(error.status);
|
|
394
|
+
return error instanceof Error && error.name === "AIInputError" && "code" in error && error.code === "capacity_unavailable" && "providerStatus" in error && typeof error.providerStatus === "number" && error.providerStatus >= 400;
|
|
395
|
+
};
|
|
396
|
+
var isPermanentAIProviderFailure = (error) => {
|
|
397
|
+
if (isProviderError(error))
|
|
398
|
+
return !error.retryable;
|
|
399
|
+
if (error instanceof Error && "providerError" in error && isProviderError(error.providerError))
|
|
400
|
+
return !error.providerError.retryable;
|
|
401
|
+
return isAIProviderFailure(error) && "providerStatus" in error && typeof error.providerStatus === "number" && error.providerStatus < 500 && ![408, 409, 425, 429].includes(error.providerStatus);
|
|
402
|
+
};
|
|
403
|
+
var inspectWithProviderFallback = async (options) => {
|
|
404
|
+
const models = providerFallbackModels(options.model, options.recovery);
|
|
405
|
+
for (const [index, model] of models.entries()) {
|
|
406
|
+
options.signal?.throwIfAborted();
|
|
407
|
+
try {
|
|
408
|
+
return { model, value: await options.inspect(model) };
|
|
409
|
+
} catch (error) {
|
|
410
|
+
options.signal?.throwIfAborted();
|
|
411
|
+
const next = models[index + 1];
|
|
412
|
+
if (!next || !isAIProviderFailure(error))
|
|
413
|
+
throw error;
|
|
414
|
+
await options.recovery?.onFallback?.({ from: model, to: next, error });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
throw new Error("No approved model available");
|
|
418
|
+
};
|
|
419
|
+
async function* streamWithProviderFallback(options) {
|
|
420
|
+
const models = [...new Set(options.models)];
|
|
421
|
+
if (!models.length)
|
|
422
|
+
throw new Error("At least one approved model is required");
|
|
423
|
+
for (const [index, model] of models.entries()) {
|
|
424
|
+
options.signal?.throwIfAborted();
|
|
425
|
+
let emitted = false;
|
|
426
|
+
try {
|
|
427
|
+
for await (const chunk of options.stream(model)) {
|
|
428
|
+
emitted = true;
|
|
429
|
+
yield chunk;
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
options.signal?.throwIfAborted();
|
|
434
|
+
const next = models[index + 1];
|
|
435
|
+
if (emitted || !next || !isAIProviderFailure(error))
|
|
436
|
+
throw error;
|
|
437
|
+
await options.onFallback?.({ from: model, to: next, error });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
269
442
|
// src/ai/inputCapacity.ts
|
|
270
443
|
class AIInputError extends Error {
|
|
271
444
|
code;
|
|
272
445
|
providerStatus;
|
|
273
446
|
requestId;
|
|
274
|
-
|
|
447
|
+
providerError;
|
|
448
|
+
constructor(code, message, providerStatus, requestId, providerError) {
|
|
275
449
|
super(message);
|
|
276
450
|
this.code = code;
|
|
277
451
|
this.providerStatus = providerStatus;
|
|
278
452
|
this.requestId = requestId;
|
|
453
|
+
this.providerError = providerError;
|
|
279
454
|
this.name = "AIInputError";
|
|
280
455
|
}
|
|
281
456
|
}
|
|
@@ -298,7 +473,7 @@ var inspectAIInput = async (provider, params) => {
|
|
|
298
473
|
provider.inputCapacity.countTokens(params)
|
|
299
474
|
]).catch((error) => {
|
|
300
475
|
if (error instanceof Error && error.name === "AIInputError" && "code" in error && (error.code === "capacity_unavailable" || error.code === "input_too_large" || error.code === "unsupported_content"))
|
|
301
|
-
throw new AIInputError(error.code, error.message, "providerStatus" in error && typeof error.providerStatus === "number" ? error.providerStatus : undefined, "requestId" in error && typeof error.requestId === "string" ? error.requestId : undefined);
|
|
476
|
+
throw new AIInputError(error.code, error.message, "providerStatus" in error && typeof error.providerStatus === "number" ? error.providerStatus : undefined, "requestId" in error && typeof error.requestId === "string" ? error.requestId : undefined, "providerError" in error && isProviderError(error.providerError) ? ProviderError.from(error.providerError, error.providerError.provider) : undefined);
|
|
302
477
|
throw error;
|
|
303
478
|
});
|
|
304
479
|
const inputTokens = inputTokenCount(tokens);
|
|
@@ -330,7 +505,7 @@ var cacheModelLimits = (load, ttlMs = 300000) => {
|
|
|
330
505
|
return limits;
|
|
331
506
|
};
|
|
332
507
|
};
|
|
333
|
-
var capacityJson = async (response) => {
|
|
508
|
+
var capacityJson = async (response, provider = "unknown") => {
|
|
334
509
|
if (!response.ok) {
|
|
335
510
|
const payload = await response.json().catch(() => {
|
|
336
511
|
return;
|
|
@@ -341,7 +516,7 @@ var capacityJson = async (response) => {
|
|
|
341
516
|
const message = nested?.message ?? record?.message;
|
|
342
517
|
const detail = typeof message === "string" ? message.slice(0, 1000) : undefined;
|
|
343
518
|
const id = response.headers.get("request-id") ?? response.headers.get("x-request-id") ?? record?.request_id;
|
|
344
|
-
throw new AIInputError("capacity_unavailable", `Model capacity request failed (${response.status}).${detail ? ` ${detail}` : ""}`, response.status, typeof id === "string" ? id.slice(0, 200) : undefined);
|
|
519
|
+
throw new AIInputError("capacity_unavailable", `Model capacity request failed (${response.status}).${detail ? ` ${detail}` : ""}`, response.status, typeof id === "string" ? id.slice(0, 200) : undefined, ProviderError.fromResponse(provider, response.status, JSON.stringify({ error: nested ?? record ?? {} })));
|
|
345
520
|
}
|
|
346
521
|
const value = await response.json();
|
|
347
522
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -395,6 +570,16 @@ var snapshot = (params) => ({
|
|
|
395
570
|
var tooLarge = () => new AIInputError("input_too_large", "The request exceeds the model's working context budget. Preserve the original input and reduce or retrieve context before retrying.");
|
|
396
571
|
var streamWithAIContext = async function* (provider, params, policy = {}, onPrepared) {
|
|
397
572
|
params.signal?.throwIfAborted();
|
|
573
|
+
if (policy !== false && policy.providerFallback) {
|
|
574
|
+
const { providerFallback, ...singlePolicy } = policy;
|
|
575
|
+
yield* streamWithProviderFallback({
|
|
576
|
+
models: providerFallbackModels(params.model, providerFallback),
|
|
577
|
+
signal: params.signal,
|
|
578
|
+
onFallback: providerFallback.onFallback,
|
|
579
|
+
stream: (model) => streamWithAIContext(provider, { ...params, model }, singlePolicy, onPrepared)
|
|
580
|
+
});
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
398
583
|
if (policy === false) {
|
|
399
584
|
onPrepared?.(params);
|
|
400
585
|
yield* provider.stream(params);
|
|
@@ -2044,6 +2229,7 @@ var streamAIWithTools = async function* (options) {
|
|
|
2044
2229
|
const policy = contextPolicy ?? (validateInput === false ? false : undefined);
|
|
2045
2230
|
const stream = streamWithAIContext(provider, request, policy, (prepared) => {
|
|
2046
2231
|
messages.splice(0, messages.length, ...prepared.messages);
|
|
2232
|
+
base.model = prepared.model;
|
|
2047
2233
|
});
|
|
2048
2234
|
const blocks = [];
|
|
2049
2235
|
const pending = [];
|
|
@@ -2197,109 +2383,6 @@ var streamAIWithTools = async function* (options) {
|
|
|
2197
2383
|
yield { ...summary, type: "done" };
|
|
2198
2384
|
return summary;
|
|
2199
2385
|
};
|
|
2200
|
-
// src/ai/errors/providerError.ts
|
|
2201
|
-
var PROVIDER_STATUS_PAGES = {
|
|
2202
|
-
anthropic: "https://status.claude.com",
|
|
2203
|
-
gemini: "https://status.cloud.google.com",
|
|
2204
|
-
google: "https://status.cloud.google.com",
|
|
2205
|
-
openai: "https://status.openai.com",
|
|
2206
|
-
openrouter: "https://status.openrouter.ai"
|
|
2207
|
-
};
|
|
2208
|
-
var RETRYABLE_STATUSES = new Set([
|
|
2209
|
-
408,
|
|
2210
|
-
409,
|
|
2211
|
-
425,
|
|
2212
|
-
429,
|
|
2213
|
-
500,
|
|
2214
|
-
502,
|
|
2215
|
-
503,
|
|
2216
|
-
504,
|
|
2217
|
-
529
|
|
2218
|
-
]);
|
|
2219
|
-
var CONNECTION_ERROR_PATTERNS = [
|
|
2220
|
-
"econnreset",
|
|
2221
|
-
"econnrefused",
|
|
2222
|
-
"etimedout",
|
|
2223
|
-
"enotfound",
|
|
2224
|
-
"eai_again",
|
|
2225
|
-
"socket hang up",
|
|
2226
|
-
"fetch failed",
|
|
2227
|
-
"network",
|
|
2228
|
-
"terminated",
|
|
2229
|
-
"timed out",
|
|
2230
|
-
"timeout",
|
|
2231
|
-
"and not retryable",
|
|
2232
|
-
"no response body"
|
|
2233
|
-
];
|
|
2234
|
-
var isAbortError = (err) => err instanceof Error && (err.name === "AbortError" || /\babort(ed)?\b/i.test(err.message));
|
|
2235
|
-
var providerStatusPage = (provider) => PROVIDER_STATUS_PAGES[provider] ?? null;
|
|
2236
|
-
var BILLING_ERROR_TYPES = new Set([
|
|
2237
|
-
"credit_balance_exhausted",
|
|
2238
|
-
"insufficient_quota",
|
|
2239
|
-
"billing_hard_limit_reached",
|
|
2240
|
-
"billing_not_active",
|
|
2241
|
-
"insufficient_credits"
|
|
2242
|
-
]);
|
|
2243
|
-
|
|
2244
|
-
class ProviderError extends Error {
|
|
2245
|
-
provider;
|
|
2246
|
-
status;
|
|
2247
|
-
type;
|
|
2248
|
-
retryable;
|
|
2249
|
-
billingUnavailable;
|
|
2250
|
-
notSent;
|
|
2251
|
-
metadata;
|
|
2252
|
-
statusPageUrl;
|
|
2253
|
-
constructor(init) {
|
|
2254
|
-
super(init.message, init.cause === undefined ? undefined : { cause: init.cause });
|
|
2255
|
-
this.name = "ProviderError";
|
|
2256
|
-
this.provider = init.provider;
|
|
2257
|
-
this.status = init.status ?? null;
|
|
2258
|
-
this.type = init.type ?? (typeof init.metadata?.code === "string" ? init.metadata.code : typeof init.metadata?.type === "string" ? init.metadata.type : null);
|
|
2259
|
-
this.billingUnavailable = BILLING_ERROR_TYPES.has(this.type ?? "") || init.status === 402;
|
|
2260
|
-
this.notSent = init.notSent === true;
|
|
2261
|
-
this.retryable = !this.billingUnavailable && init.retryable;
|
|
2262
|
-
this.metadata = init.metadata;
|
|
2263
|
-
this.statusPageUrl = providerStatusPage(init.provider);
|
|
2264
|
-
}
|
|
2265
|
-
static fromResponse(provider, status, body, type) {
|
|
2266
|
-
let metadata;
|
|
2267
|
-
try {
|
|
2268
|
-
const parsed = JSON.parse(body);
|
|
2269
|
-
const details = parsed?.error ?? parsed;
|
|
2270
|
-
if (details && typeof details === "object" && !Array.isArray(details))
|
|
2271
|
-
metadata = details;
|
|
2272
|
-
} catch {}
|
|
2273
|
-
return new ProviderError({
|
|
2274
|
-
metadata,
|
|
2275
|
-
message: `${capitalize(provider)} API error ${status}: ${body}`,
|
|
2276
|
-
provider,
|
|
2277
|
-
retryable: RETRYABLE_STATUSES.has(status),
|
|
2278
|
-
status,
|
|
2279
|
-
type: type ?? null
|
|
2280
|
-
});
|
|
2281
|
-
}
|
|
2282
|
-
static from(err, provider) {
|
|
2283
|
-
if (err instanceof ProviderError)
|
|
2284
|
-
return err;
|
|
2285
|
-
if (isAbortError(err))
|
|
2286
|
-
throw err;
|
|
2287
|
-
const rawMessage = err instanceof Error ? err.message : String(err ?? "");
|
|
2288
|
-
const lower = rawMessage.toLowerCase();
|
|
2289
|
-
const statusMatch = lower.match(/api error\s+(\d{3})/);
|
|
2290
|
-
const status = statusMatch ? Number(statusMatch[1]) : null;
|
|
2291
|
-
const retryable = status !== null && RETRYABLE_STATUSES.has(status) || status === null && CONNECTION_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
|
|
2292
|
-
return new ProviderError({
|
|
2293
|
-
cause: err,
|
|
2294
|
-
message: rawMessage || `${capitalize(provider)} request failed`,
|
|
2295
|
-
provider,
|
|
2296
|
-
retryable,
|
|
2297
|
-
status
|
|
2298
|
-
});
|
|
2299
|
-
}
|
|
2300
|
-
}
|
|
2301
|
-
var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
2302
|
-
|
|
2303
2386
|
// src/ai/providerProxy.ts
|
|
2304
2387
|
var DEFAULT_HEARTBEAT_MS2 = 5000;
|
|
2305
2388
|
var encoder = new TextEncoder;
|
|
@@ -4577,6 +4660,76 @@ var createAIStream = (path, conversationId) => {
|
|
|
4577
4660
|
}
|
|
4578
4661
|
};
|
|
4579
4662
|
};
|
|
4663
|
+
// src/ai/providers/openaiSchema.ts
|
|
4664
|
+
var record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4665
|
+
var openaiSchema = (schema) => {
|
|
4666
|
+
const result = { ...schema };
|
|
4667
|
+
for (const key of [
|
|
4668
|
+
"properties",
|
|
4669
|
+
"$defs",
|
|
4670
|
+
"definitions",
|
|
4671
|
+
"patternProperties"
|
|
4672
|
+
]) {
|
|
4673
|
+
const value = result[key];
|
|
4674
|
+
if (record2(value))
|
|
4675
|
+
result[key] = Object.fromEntries(Object.entries(value).map(([name, child]) => [
|
|
4676
|
+
name,
|
|
4677
|
+
record2(child) ? openaiSchema(child) : child
|
|
4678
|
+
]));
|
|
4679
|
+
}
|
|
4680
|
+
for (const key of [
|
|
4681
|
+
"items",
|
|
4682
|
+
"additionalProperties",
|
|
4683
|
+
"not",
|
|
4684
|
+
"if",
|
|
4685
|
+
"then",
|
|
4686
|
+
"else"
|
|
4687
|
+
])
|
|
4688
|
+
if (record2(result[key]))
|
|
4689
|
+
result[key] = openaiSchema(result[key]);
|
|
4690
|
+
for (const key of ["anyOf", "oneOf", "allOf", "prefixItems"])
|
|
4691
|
+
if (Array.isArray(result[key]))
|
|
4692
|
+
result[key] = result[key].map((child) => record2(child) ? openaiSchema(child) : child);
|
|
4693
|
+
const names = result.propertyNames;
|
|
4694
|
+
if (record2(names) && Array.isArray(names.enum) && names.enum.every((key) => typeof key === "string") && Object.keys(names).every((key) => ["enum", "type", "description", "title"].includes(key)) && !result.patternProperties) {
|
|
4695
|
+
const existing = record2(result.properties) ? result.properties : {};
|
|
4696
|
+
const additional = result.additionalProperties;
|
|
4697
|
+
const entries = names.enum.filter((key) => Object.hasOwn(existing, key) || additional !== false).map((key) => [
|
|
4698
|
+
key,
|
|
4699
|
+
Object.hasOwn(existing, key) ? existing[key] : record2(additional) ? additional : {}
|
|
4700
|
+
]);
|
|
4701
|
+
result.properties = Object.fromEntries(entries);
|
|
4702
|
+
result.additionalProperties = false;
|
|
4703
|
+
delete result.propertyNames;
|
|
4704
|
+
}
|
|
4705
|
+
return result;
|
|
4706
|
+
};
|
|
4707
|
+
var isStrictCompatible = (schema) => {
|
|
4708
|
+
if (Array.isArray(schema))
|
|
4709
|
+
return schema.every(isStrictCompatible);
|
|
4710
|
+
if (!record2(schema))
|
|
4711
|
+
return true;
|
|
4712
|
+
if (schema.propertyNames || schema.patternProperties)
|
|
4713
|
+
return false;
|
|
4714
|
+
const properties = record2(schema.properties) ? schema.properties : {};
|
|
4715
|
+
if (schema.type === "object" || schema.properties || schema.additionalProperties !== undefined) {
|
|
4716
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
4717
|
+
if (schema.additionalProperties !== false || !Object.keys(properties).every((key) => required.includes(key)))
|
|
4718
|
+
return false;
|
|
4719
|
+
}
|
|
4720
|
+
const nested = [
|
|
4721
|
+
schema.items,
|
|
4722
|
+
schema.anyOf,
|
|
4723
|
+
schema.oneOf,
|
|
4724
|
+
schema.allOf,
|
|
4725
|
+
schema.additionalProperties
|
|
4726
|
+
];
|
|
4727
|
+
for (const map of [properties, schema.$defs, schema.definitions])
|
|
4728
|
+
if (record2(map))
|
|
4729
|
+
nested.push(...Object.values(map));
|
|
4730
|
+
return nested.every(isStrictCompatible);
|
|
4731
|
+
};
|
|
4732
|
+
|
|
4580
4733
|
// src/ai/providers/openaiCapacity.ts
|
|
4581
4734
|
var LIMITS = {
|
|
4582
4735
|
"gpt-4.1": {
|
|
@@ -4799,7 +4952,7 @@ var openaiInputCapacity = (config) => ({
|
|
|
4799
4952
|
body: JSON.stringify(counted),
|
|
4800
4953
|
signal: params.signal ? AbortSignal.any([params.signal, AbortSignal.timeout(15000)]) : AbortSignal.timeout(15000)
|
|
4801
4954
|
});
|
|
4802
|
-
return inputTokenCount((await capacityJson(response)).input_tokens);
|
|
4955
|
+
return inputTokenCount((await capacityJson(response, "openai")).input_tokens);
|
|
4803
4956
|
}
|
|
4804
4957
|
});
|
|
4805
4958
|
|
|
@@ -4932,6 +5085,29 @@ var withResilience = (provider, providerName = "unknown") => {
|
|
|
4932
5085
|
let billingFailure = null;
|
|
4933
5086
|
let billingRetryAt = 0;
|
|
4934
5087
|
let billingProbe = false;
|
|
5088
|
+
const inspectAvailable = async (inspect) => {
|
|
5089
|
+
if (billingFailure && Date.now() < billingRetryAt) {
|
|
5090
|
+
throw new ProviderError({
|
|
5091
|
+
provider: providerName,
|
|
5092
|
+
message: "Provider billing is unavailable",
|
|
5093
|
+
type: billingFailure.type,
|
|
5094
|
+
status: billingFailure.status,
|
|
5095
|
+
metadata: billingFailure.metadata,
|
|
5096
|
+
retryable: false,
|
|
5097
|
+
notSent: true
|
|
5098
|
+
});
|
|
5099
|
+
}
|
|
5100
|
+
try {
|
|
5101
|
+
return await inspect();
|
|
5102
|
+
} catch (error) {
|
|
5103
|
+
const nested = error instanceof Error && "providerError" in error ? error.providerError : error;
|
|
5104
|
+
if (isProviderError(nested) && nested.billingUnavailable) {
|
|
5105
|
+
billingFailure = ProviderError.from(nested, providerName);
|
|
5106
|
+
billingRetryAt = Date.now() + billingCooldownMs;
|
|
5107
|
+
}
|
|
5108
|
+
throw error;
|
|
5109
|
+
}
|
|
5110
|
+
};
|
|
4935
5111
|
const billingCooldownMs = 5 * 60000;
|
|
4936
5112
|
const attempt = async function* (params, attemptNo) {
|
|
4937
5113
|
let yielded = false;
|
|
@@ -4959,33 +5135,39 @@ var withResilience = (provider, providerName = "unknown") => {
|
|
|
4959
5135
|
if (providerError.retryable)
|
|
4960
5136
|
noteFailure(providerName, providerError);
|
|
4961
5137
|
throw providerError;
|
|
5138
|
+
} finally {
|
|
5139
|
+
billingProbe = false;
|
|
4962
5140
|
}
|
|
4963
5141
|
};
|
|
4964
5142
|
return {
|
|
4965
5143
|
...provider,
|
|
4966
|
-
|
|
5144
|
+
...provider.inputCapacity ? {
|
|
5145
|
+
inputCapacity: {
|
|
5146
|
+
...provider.inputCapacity,
|
|
5147
|
+
getLimits: (params) => inspectAvailable(() => provider.inputCapacity.getLimits(params)),
|
|
5148
|
+
countTokens: (params) => inspectAvailable(() => provider.inputCapacity.countTokens(params))
|
|
5149
|
+
}
|
|
5150
|
+
} : {},
|
|
5151
|
+
stream: async function* (params) {
|
|
4967
5152
|
if (billingFailure && (Date.now() < billingRetryAt || billingProbe)) {
|
|
4968
5153
|
const failure = billingFailure;
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
}();
|
|
5154
|
+
throw new ProviderError({
|
|
5155
|
+
provider: providerName,
|
|
5156
|
+
message: "Provider billing is unavailable",
|
|
5157
|
+
type: failure.type,
|
|
5158
|
+
status: failure.status,
|
|
5159
|
+
metadata: failure.metadata,
|
|
5160
|
+
retryable: false,
|
|
5161
|
+
notSent: true
|
|
5162
|
+
});
|
|
4979
5163
|
}
|
|
4980
|
-
if (billingFailure)
|
|
4981
|
-
billingProbe = true;
|
|
4982
5164
|
const tripped = circuitOpen(providerName);
|
|
4983
5165
|
if (tripped) {
|
|
4984
|
-
|
|
4985
|
-
throw tripped;
|
|
4986
|
-
}();
|
|
5166
|
+
throw tripped;
|
|
4987
5167
|
}
|
|
4988
|
-
|
|
5168
|
+
if (billingFailure)
|
|
5169
|
+
billingProbe = true;
|
|
5170
|
+
yield* attempt(params, 0);
|
|
4989
5171
|
}
|
|
4990
5172
|
};
|
|
4991
5173
|
};
|
|
@@ -4994,7 +5176,7 @@ var withResilience = (provider, providerName = "unknown") => {
|
|
|
4994
5176
|
var instrumentAIProvider = (provider, providerName) => {
|
|
4995
5177
|
const resilient = withResilience(provider, providerName);
|
|
4996
5178
|
return {
|
|
4997
|
-
...
|
|
5179
|
+
...resilient,
|
|
4998
5180
|
stream: (params) => {
|
|
4999
5181
|
if (!params.onUsage && !params.onSpan) {
|
|
5000
5182
|
return resilient.stream(params);
|
|
@@ -5354,30 +5536,11 @@ var buildInput = (messages) => {
|
|
|
5354
5536
|
}
|
|
5355
5537
|
return input;
|
|
5356
5538
|
};
|
|
5357
|
-
var isStrictCompatible = (schema) => {
|
|
5358
|
-
if (Array.isArray(schema))
|
|
5359
|
-
return schema.every(isStrictCompatible);
|
|
5360
|
-
if (!isRecord2(schema))
|
|
5361
|
-
return true;
|
|
5362
|
-
const { properties } = schema;
|
|
5363
|
-
if (isRecord2(properties)) {
|
|
5364
|
-
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
5365
|
-
if (schema.additionalProperties !== false)
|
|
5366
|
-
return false;
|
|
5367
|
-
if (!Object.keys(properties).every((key) => required.includes(key)))
|
|
5368
|
-
return false;
|
|
5369
|
-
}
|
|
5370
|
-
const nested = [schema.items, schema.anyOf];
|
|
5371
|
-
for (const map of [properties, schema.$defs, schema.definitions])
|
|
5372
|
-
if (isRecord2(map))
|
|
5373
|
-
nested.push(...Object.values(map));
|
|
5374
|
-
return nested.every(isStrictCompatible);
|
|
5375
|
-
};
|
|
5376
5539
|
var mapToolDefinition = (tool) => ({
|
|
5377
5540
|
description: tool.description,
|
|
5378
5541
|
name: tool.name,
|
|
5379
|
-
parameters: tool.input_schema,
|
|
5380
|
-
strict: isStrictCompatible(tool.input_schema),
|
|
5542
|
+
parameters: openaiSchema(tool.input_schema),
|
|
5543
|
+
strict: isStrictCompatible(openaiSchema(tool.input_schema)),
|
|
5381
5544
|
type: "function"
|
|
5382
5545
|
});
|
|
5383
5546
|
var buildTools = (tools, isImageModel) => {
|
|
@@ -5434,7 +5597,7 @@ var buildResponsesRequestBody = (params, isImageModel, capabilityModel = params.
|
|
|
5434
5597
|
body.text = {
|
|
5435
5598
|
format: {
|
|
5436
5599
|
name: params.responseFormat.name,
|
|
5437
|
-
schema: params.responseFormat.schema,
|
|
5600
|
+
schema: openaiSchema(params.responseFormat.schema),
|
|
5438
5601
|
strict: params.responseFormat.strict ?? true,
|
|
5439
5602
|
type: "json_schema"
|
|
5440
5603
|
}
|
|
@@ -5901,7 +6064,7 @@ var mapToolDefinitions = (tools) => tools.map((tool) => ({
|
|
|
5901
6064
|
function: {
|
|
5902
6065
|
description: tool.description,
|
|
5903
6066
|
name: tool.name,
|
|
5904
|
-
parameters: tool.input_schema
|
|
6067
|
+
parameters: openaiSchema(tool.input_schema)
|
|
5905
6068
|
},
|
|
5906
6069
|
type: "function"
|
|
5907
6070
|
}));
|
|
@@ -6027,7 +6190,7 @@ var buildRequestBody = (params, capabilityModel = params.model, providerName = "
|
|
|
6027
6190
|
body.response_format = {
|
|
6028
6191
|
json_schema: {
|
|
6029
6192
|
name: params.responseFormat.name,
|
|
6030
|
-
schema: params.responseFormat.schema,
|
|
6193
|
+
schema: openaiSchema(params.responseFormat.schema),
|
|
6031
6194
|
strict: params.responseFormat.strict ?? true
|
|
6032
6195
|
},
|
|
6033
6196
|
type: "json_schema"
|
|
@@ -7528,7 +7691,7 @@ var anthropic = (config) => {
|
|
|
7528
7691
|
headers,
|
|
7529
7692
|
body: body ? JSON.stringify(body) : undefined,
|
|
7530
7693
|
signal: params.signal ? AbortSignal.any([params.signal, AbortSignal.timeout(15000)]) : AbortSignal.timeout(15000)
|
|
7531
|
-
}));
|
|
7694
|
+
}), "anthropic");
|
|
7532
7695
|
};
|
|
7533
7696
|
const countingRequest = (params) => {
|
|
7534
7697
|
const built = buildRequestBody3(params, configuredMax, promptCaching);
|
|
@@ -8900,8 +9063,17 @@ var hashText = async (text) => {
|
|
|
8900
9063
|
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
8901
9064
|
};
|
|
8902
9065
|
var prepareAITextInputInternal = async (provider, params, options, stopAfterSection) => {
|
|
9066
|
+
if (options.providerFallback)
|
|
9067
|
+
params = { ...params };
|
|
8903
9068
|
const inspectBudget = async (_provider, request) => {
|
|
8904
|
-
const
|
|
9069
|
+
const selected = await inspectWithProviderFallback({
|
|
9070
|
+
model: request.model,
|
|
9071
|
+
recovery: options.providerFallback,
|
|
9072
|
+
signal: request.signal,
|
|
9073
|
+
inspect: (model) => inspectAIContext(_provider, { ...request, model }, options.contextBudget)
|
|
9074
|
+
});
|
|
9075
|
+
request.model = selected.model;
|
|
9076
|
+
const inspected = selected.value;
|
|
8905
9077
|
return {
|
|
8906
9078
|
...inspected,
|
|
8907
9079
|
fits: inspected.withinWorkingLimit,
|
|
@@ -8939,28 +9111,43 @@ var prepareAITextInputInternal = async (provider, params, options, stopAfterSect
|
|
|
8939
9111
|
throw new AIInputError("input_too_large", "The instructions and tools leave no room for input in this model.");
|
|
8940
9112
|
if (options.preparationModel !== undefined && !options.preparationModel.trim())
|
|
8941
9113
|
throw new AIInputError("capacity_unavailable", "A preparation model must be nonempty.");
|
|
8942
|
-
const
|
|
8943
|
-
|
|
8944
|
-
model: preparationModel,
|
|
9114
|
+
const preparationProbe = {
|
|
9115
|
+
model: options.preparationModel ?? params.model,
|
|
8945
9116
|
signal: params.signal,
|
|
8946
9117
|
maxTokens: 1,
|
|
8947
9118
|
messages: [{ role: "user", content: "." }]
|
|
8948
|
-
}
|
|
9119
|
+
};
|
|
9120
|
+
const preparationCapacity = options.preparationModel === undefined ? capacity : await inspectBudget(provider, preparationProbe);
|
|
9121
|
+
const preparationModel = preparationProbe.model;
|
|
8949
9122
|
const taskHash = await hashText(JSON.stringify({
|
|
8950
|
-
preparationPolicyVersion: 3,
|
|
8951
|
-
preparationModel: options.preparationModel,
|
|
8952
|
-
model: params.model,
|
|
9123
|
+
preparationPolicyVersion: options.providerFallback ? 4 : 3,
|
|
9124
|
+
preparationModel: options.providerFallback ? undefined : options.preparationModel,
|
|
9125
|
+
model: options.providerFallback ? undefined : params.model,
|
|
8953
9126
|
systemPrompt: params.systemPrompt,
|
|
8954
9127
|
tools: params.tools,
|
|
8955
9128
|
compactedContext: options.compactedContext,
|
|
8956
9129
|
contextBudget: options.contextBudget
|
|
8957
9130
|
}));
|
|
9131
|
+
const compatibleHashes = new Set([taskHash]);
|
|
9132
|
+
if (options.providerFallback && options.checkpoint) {
|
|
9133
|
+
for (const model of providerFallbackModels(params.model, options.providerFallback)) {
|
|
9134
|
+
compatibleHashes.add(await hashText(JSON.stringify({
|
|
9135
|
+
preparationPolicyVersion: 3,
|
|
9136
|
+
preparationModel: options.preparationModel,
|
|
9137
|
+
model,
|
|
9138
|
+
systemPrompt: params.systemPrompt,
|
|
9139
|
+
tools: params.tools,
|
|
9140
|
+
compactedContext: options.compactedContext,
|
|
9141
|
+
contextBudget: options.contextBudget
|
|
9142
|
+
})));
|
|
9143
|
+
}
|
|
9144
|
+
}
|
|
8958
9145
|
const last = params.messages.at(-1);
|
|
8959
9146
|
let notes = "";
|
|
8960
9147
|
let offset = 0;
|
|
8961
9148
|
let sectionsProcessed = 0;
|
|
8962
9149
|
const saved = options.checkpoint;
|
|
8963
|
-
if (saved?.version === 1 && saved.taskHash
|
|
9150
|
+
if (saved?.version === 1 && compatibleHashes.has(saved.taskHash) && Number.isSafeInteger(saved.processedCharacters) && saved.processedCharacters > 0 && saved.processedCharacters <= text.length && Number.isSafeInteger(saved.sectionsProcessed) && saved.sectionsProcessed > 0 && typeof saved.notes === "string" && saved.notes.trim() && saved.prefixHash === await hashText(text.slice(0, saved.processedCharacters))) {
|
|
8964
9151
|
notes = saved.notes;
|
|
8965
9152
|
offset = saved.processedCharacters;
|
|
8966
9153
|
sectionsProcessed = saved.sectionsProcessed;
|
|
@@ -9030,7 +9217,10 @@ var prepareAITextInputInternal = async (provider, params, options, stopAfterSect
|
|
|
9030
9217
|
params.signal?.throwIfAborted();
|
|
9031
9218
|
let updated = "";
|
|
9032
9219
|
let ended = false;
|
|
9033
|
-
for await (const chunk of
|
|
9220
|
+
for await (const chunk of options.providerFallback ? streamWithAIContext(provider, request, {
|
|
9221
|
+
...options.contextBudget,
|
|
9222
|
+
providerFallback: options.providerFallback
|
|
9223
|
+
}) : provider.stream(request)) {
|
|
9034
9224
|
if (chunk.type === "text")
|
|
9035
9225
|
updated += chunk.content;
|
|
9036
9226
|
if (chunk.type === "done") {
|
|
@@ -9110,7 +9300,7 @@ var boundary = (text, offset) => {
|
|
|
9110
9300
|
const next = text.charCodeAt(offset);
|
|
9111
9301
|
return code >= 55296 && code <= 56319 && next >= 56320 && next <= 57343 ? offset - 1 : offset;
|
|
9112
9302
|
};
|
|
9113
|
-
var
|
|
9303
|
+
var record3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
9114
9304
|
var createAITextSource = (messages) => {
|
|
9115
9305
|
const sources = messages.map((message, index) => {
|
|
9116
9306
|
if (typeof message.content !== "string")
|
|
@@ -9177,7 +9367,7 @@ var createAITextSource = (messages) => {
|
|
|
9177
9367
|
additionalProperties: false
|
|
9178
9368
|
},
|
|
9179
9369
|
handler: (input) => {
|
|
9180
|
-
const value =
|
|
9370
|
+
const value = record3(input);
|
|
9181
9371
|
if (typeof value.query !== "string")
|
|
9182
9372
|
return "Provide a text query.";
|
|
9183
9373
|
return JSON.stringify({
|
|
@@ -9200,7 +9390,7 @@ var createAITextSource = (messages) => {
|
|
|
9200
9390
|
additionalProperties: false
|
|
9201
9391
|
},
|
|
9202
9392
|
handler: (input) => {
|
|
9203
|
-
const value =
|
|
9393
|
+
const value = record3(input);
|
|
9204
9394
|
if (typeof value.sourceId !== "string" || typeof value.start !== "number")
|
|
9205
9395
|
return "Provide a source ID and start offset.";
|
|
9206
9396
|
return JSON.stringify({
|
|
@@ -9269,29 +9459,6 @@ var createAIProviderRouter = (routes) => {
|
|
|
9269
9459
|
stream: (params) => resolve(params).stream(params)
|
|
9270
9460
|
};
|
|
9271
9461
|
};
|
|
9272
|
-
// src/ai/providerFallback.ts
|
|
9273
|
-
async function* streamWithProviderFallback(options) {
|
|
9274
|
-
const models = [...new Set(options.models)];
|
|
9275
|
-
if (!models.length)
|
|
9276
|
-
throw new Error("At least one approved model is required");
|
|
9277
|
-
for (const [index, model] of models.entries()) {
|
|
9278
|
-
options.signal?.throwIfAborted();
|
|
9279
|
-
let emitted = false;
|
|
9280
|
-
try {
|
|
9281
|
-
for await (const chunk of options.stream(model)) {
|
|
9282
|
-
emitted = true;
|
|
9283
|
-
yield chunk;
|
|
9284
|
-
}
|
|
9285
|
-
return;
|
|
9286
|
-
} catch (error) {
|
|
9287
|
-
options.signal?.throwIfAborted();
|
|
9288
|
-
const next = models[index + 1];
|
|
9289
|
-
if (emitted || !next || !(error instanceof ProviderError) || !(error.billingUnavailable || error.retryable || error.notSent))
|
|
9290
|
-
throw error;
|
|
9291
|
-
await options.onFallback?.({ from: model, to: next, error });
|
|
9292
|
-
}
|
|
9293
|
-
}
|
|
9294
|
-
}
|
|
9295
9462
|
export {
|
|
9296
9463
|
AIInputError,
|
|
9297
9464
|
BUILTIN_UI_CARDS,
|
|
@@ -9362,7 +9529,11 @@ export {
|
|
|
9362
9529
|
google,
|
|
9363
9530
|
inspectAIContext,
|
|
9364
9531
|
inspectAIInput,
|
|
9532
|
+
inspectWithProviderFallback,
|
|
9365
9533
|
isAIContextRejection,
|
|
9534
|
+
isAIProviderFailure,
|
|
9535
|
+
isPermanentAIProviderFailure,
|
|
9536
|
+
isProviderError,
|
|
9366
9537
|
meta,
|
|
9367
9538
|
mistralai,
|
|
9368
9539
|
moonshot,
|
|
@@ -9411,5 +9582,5 @@ export {
|
|
|
9411
9582
|
xai
|
|
9412
9583
|
};
|
|
9413
9584
|
|
|
9414
|
-
//# debugId=
|
|
9585
|
+
//# debugId=3224ABE1580A431764756E2164756E21
|
|
9415
9586
|
//# sourceMappingURL=index.js.map
|