@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/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
- constructor(code, message, providerStatus, requestId) {
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
- stream: (params) => {
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
- return async function* () {
4970
- throw new ProviderError({
4971
- provider: providerName,
4972
- message: "Provider billing is unavailable",
4973
- type: failure.type,
4974
- status: failure.status,
4975
- retryable: false,
4976
- notSent: true
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
- return async function* () {
4985
- throw tripped;
4986
- }();
5166
+ throw tripped;
4987
5167
  }
4988
- return attempt(params, 0);
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
- ...provider,
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 inspected = await inspectAIContext(_provider, request, options.contextBudget);
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 preparationModel = options.preparationModel ?? params.model;
8943
- const preparationCapacity = options.preparationModel === undefined ? capacity : await inspectBudget(provider, {
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 === 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))) {
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 provider.stream(request)) {
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 record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
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 = record2(input);
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 = record2(input);
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=15DF9981B943EAAB64756E2164756E21
9585
+ //# debugId=3224ABE1580A431764756E2164756E21
9415
9586
  //# sourceMappingURL=index.js.map