@absolutejs/ai 0.6.5 → 0.6.7
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 +12 -0
- package/README.md +18 -0
- package/changelog.json +26 -0
- package/dist/ai/index.js +363 -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 +1 -1
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,18 @@ 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
|
+
const requestedModel = params.model;
|
|
9067
|
+
if (options.providerFallback)
|
|
9068
|
+
params = { ...params };
|
|
8903
9069
|
const inspectBudget = async (_provider, request) => {
|
|
8904
|
-
const
|
|
9070
|
+
const selected = await inspectWithProviderFallback({
|
|
9071
|
+
model: request.model,
|
|
9072
|
+
recovery: options.providerFallback,
|
|
9073
|
+
signal: request.signal,
|
|
9074
|
+
inspect: (model) => inspectAIContext(_provider, { ...request, model }, options.contextBudget)
|
|
9075
|
+
});
|
|
9076
|
+
request.model = selected.model;
|
|
9077
|
+
const inspected = selected.value;
|
|
8905
9078
|
return {
|
|
8906
9079
|
...inspected,
|
|
8907
9080
|
fits: inspected.withinWorkingLimit,
|
|
@@ -8939,28 +9112,46 @@ var prepareAITextInputInternal = async (provider, params, options, stopAfterSect
|
|
|
8939
9112
|
throw new AIInputError("input_too_large", "The instructions and tools leave no room for input in this model.");
|
|
8940
9113
|
if (options.preparationModel !== undefined && !options.preparationModel.trim())
|
|
8941
9114
|
throw new AIInputError("capacity_unavailable", "A preparation model must be nonempty.");
|
|
8942
|
-
const
|
|
8943
|
-
|
|
8944
|
-
model: preparationModel,
|
|
9115
|
+
const preparationProbe = {
|
|
9116
|
+
model: options.preparationModel ?? params.model,
|
|
8945
9117
|
signal: params.signal,
|
|
8946
9118
|
maxTokens: 1,
|
|
8947
9119
|
messages: [{ role: "user", content: "." }]
|
|
8948
|
-
}
|
|
9120
|
+
};
|
|
9121
|
+
const preparationCapacity = options.preparationModel === undefined ? capacity : await inspectBudget(provider, preparationProbe);
|
|
9122
|
+
const preparationModel = preparationProbe.model;
|
|
8949
9123
|
const taskHash = await hashText(JSON.stringify({
|
|
8950
|
-
preparationPolicyVersion: 3,
|
|
8951
|
-
preparationModel: options.preparationModel,
|
|
8952
|
-
model: params.model,
|
|
9124
|
+
preparationPolicyVersion: options.providerFallback ? 4 : 3,
|
|
9125
|
+
preparationModel: options.providerFallback ? undefined : options.preparationModel,
|
|
9126
|
+
model: options.providerFallback ? undefined : params.model,
|
|
8953
9127
|
systemPrompt: params.systemPrompt,
|
|
8954
9128
|
tools: params.tools,
|
|
8955
9129
|
compactedContext: options.compactedContext,
|
|
8956
9130
|
contextBudget: options.contextBudget
|
|
8957
9131
|
}));
|
|
9132
|
+
const compatibleHashes = new Set([taskHash]);
|
|
9133
|
+
if (options.providerFallback && options.checkpoint) {
|
|
9134
|
+
for (const model of new Set([
|
|
9135
|
+
...providerFallbackModels(requestedModel, options.providerFallback),
|
|
9136
|
+
...providerFallbackModels(params.model, options.providerFallback)
|
|
9137
|
+
])) {
|
|
9138
|
+
compatibleHashes.add(await hashText(JSON.stringify({
|
|
9139
|
+
preparationPolicyVersion: 3,
|
|
9140
|
+
preparationModel: options.preparationModel,
|
|
9141
|
+
model,
|
|
9142
|
+
systemPrompt: params.systemPrompt,
|
|
9143
|
+
tools: params.tools,
|
|
9144
|
+
compactedContext: options.compactedContext,
|
|
9145
|
+
contextBudget: options.contextBudget
|
|
9146
|
+
})));
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
8958
9149
|
const last = params.messages.at(-1);
|
|
8959
9150
|
let notes = "";
|
|
8960
9151
|
let offset = 0;
|
|
8961
9152
|
let sectionsProcessed = 0;
|
|
8962
9153
|
const saved = options.checkpoint;
|
|
8963
|
-
if (saved?.version === 1 && saved.taskHash
|
|
9154
|
+
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
9155
|
notes = saved.notes;
|
|
8965
9156
|
offset = saved.processedCharacters;
|
|
8966
9157
|
sectionsProcessed = saved.sectionsProcessed;
|
|
@@ -9030,7 +9221,10 @@ var prepareAITextInputInternal = async (provider, params, options, stopAfterSect
|
|
|
9030
9221
|
params.signal?.throwIfAborted();
|
|
9031
9222
|
let updated = "";
|
|
9032
9223
|
let ended = false;
|
|
9033
|
-
for await (const chunk of
|
|
9224
|
+
for await (const chunk of options.providerFallback ? streamWithAIContext(provider, request, {
|
|
9225
|
+
...options.contextBudget,
|
|
9226
|
+
providerFallback: options.providerFallback
|
|
9227
|
+
}) : provider.stream(request)) {
|
|
9034
9228
|
if (chunk.type === "text")
|
|
9035
9229
|
updated += chunk.content;
|
|
9036
9230
|
if (chunk.type === "done") {
|
|
@@ -9110,7 +9304,7 @@ var boundary = (text, offset) => {
|
|
|
9110
9304
|
const next = text.charCodeAt(offset);
|
|
9111
9305
|
return code >= 55296 && code <= 56319 && next >= 56320 && next <= 57343 ? offset - 1 : offset;
|
|
9112
9306
|
};
|
|
9113
|
-
var
|
|
9307
|
+
var record3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
9114
9308
|
var createAITextSource = (messages) => {
|
|
9115
9309
|
const sources = messages.map((message, index) => {
|
|
9116
9310
|
if (typeof message.content !== "string")
|
|
@@ -9177,7 +9371,7 @@ var createAITextSource = (messages) => {
|
|
|
9177
9371
|
additionalProperties: false
|
|
9178
9372
|
},
|
|
9179
9373
|
handler: (input) => {
|
|
9180
|
-
const value =
|
|
9374
|
+
const value = record3(input);
|
|
9181
9375
|
if (typeof value.query !== "string")
|
|
9182
9376
|
return "Provide a text query.";
|
|
9183
9377
|
return JSON.stringify({
|
|
@@ -9200,7 +9394,7 @@ var createAITextSource = (messages) => {
|
|
|
9200
9394
|
additionalProperties: false
|
|
9201
9395
|
},
|
|
9202
9396
|
handler: (input) => {
|
|
9203
|
-
const value =
|
|
9397
|
+
const value = record3(input);
|
|
9204
9398
|
if (typeof value.sourceId !== "string" || typeof value.start !== "number")
|
|
9205
9399
|
return "Provide a source ID and start offset.";
|
|
9206
9400
|
return JSON.stringify({
|
|
@@ -9269,29 +9463,6 @@ var createAIProviderRouter = (routes) => {
|
|
|
9269
9463
|
stream: (params) => resolve(params).stream(params)
|
|
9270
9464
|
};
|
|
9271
9465
|
};
|
|
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
9466
|
export {
|
|
9296
9467
|
AIInputError,
|
|
9297
9468
|
BUILTIN_UI_CARDS,
|
|
@@ -9362,7 +9533,11 @@ export {
|
|
|
9362
9533
|
google,
|
|
9363
9534
|
inspectAIContext,
|
|
9364
9535
|
inspectAIInput,
|
|
9536
|
+
inspectWithProviderFallback,
|
|
9365
9537
|
isAIContextRejection,
|
|
9538
|
+
isAIProviderFailure,
|
|
9539
|
+
isPermanentAIProviderFailure,
|
|
9540
|
+
isProviderError,
|
|
9366
9541
|
meta,
|
|
9367
9542
|
mistralai,
|
|
9368
9543
|
moonshot,
|
|
@@ -9411,5 +9586,5 @@ export {
|
|
|
9411
9586
|
xai
|
|
9412
9587
|
};
|
|
9413
9588
|
|
|
9414
|
-
//# debugId=
|
|
9589
|
+
//# debugId=09EF9D42933CF31D64756E2164756E21
|
|
9415
9590
|
//# sourceMappingURL=index.js.map
|