@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.
@@ -1,14 +1,201 @@
1
1
  // @bun
2
+ // src/ai/providers/openaiSchema.ts
3
+ var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
4
+ var openaiSchema = (schema) => {
5
+ const result = { ...schema };
6
+ for (const key of [
7
+ "properties",
8
+ "$defs",
9
+ "definitions",
10
+ "patternProperties"
11
+ ]) {
12
+ const value = result[key];
13
+ if (record(value))
14
+ result[key] = Object.fromEntries(Object.entries(value).map(([name, child]) => [
15
+ name,
16
+ record(child) ? openaiSchema(child) : child
17
+ ]));
18
+ }
19
+ for (const key of [
20
+ "items",
21
+ "additionalProperties",
22
+ "not",
23
+ "if",
24
+ "then",
25
+ "else"
26
+ ])
27
+ if (record(result[key]))
28
+ result[key] = openaiSchema(result[key]);
29
+ for (const key of ["anyOf", "oneOf", "allOf", "prefixItems"])
30
+ if (Array.isArray(result[key]))
31
+ result[key] = result[key].map((child) => record(child) ? openaiSchema(child) : child);
32
+ const names = result.propertyNames;
33
+ if (record(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) {
34
+ const existing = record(result.properties) ? result.properties : {};
35
+ const additional = result.additionalProperties;
36
+ const entries = names.enum.filter((key) => Object.hasOwn(existing, key) || additional !== false).map((key) => [
37
+ key,
38
+ Object.hasOwn(existing, key) ? existing[key] : record(additional) ? additional : {}
39
+ ]);
40
+ result.properties = Object.fromEntries(entries);
41
+ result.additionalProperties = false;
42
+ delete result.propertyNames;
43
+ }
44
+ return result;
45
+ };
46
+ var isStrictCompatible = (schema) => {
47
+ if (Array.isArray(schema))
48
+ return schema.every(isStrictCompatible);
49
+ if (!record(schema))
50
+ return true;
51
+ if (schema.propertyNames || schema.patternProperties)
52
+ return false;
53
+ const properties = record(schema.properties) ? schema.properties : {};
54
+ if (schema.type === "object" || schema.properties || schema.additionalProperties !== undefined) {
55
+ const required = Array.isArray(schema.required) ? schema.required : [];
56
+ if (schema.additionalProperties !== false || !Object.keys(properties).every((key) => required.includes(key)))
57
+ return false;
58
+ }
59
+ const nested = [
60
+ schema.items,
61
+ schema.anyOf,
62
+ schema.oneOf,
63
+ schema.allOf,
64
+ schema.additionalProperties
65
+ ];
66
+ for (const map of [properties, schema.$defs, schema.definitions])
67
+ if (record(map))
68
+ nested.push(...Object.values(map));
69
+ return nested.every(isStrictCompatible);
70
+ };
71
+
72
+ // src/ai/errors/providerError.ts
73
+ var PROVIDER_STATUS_PAGES = {
74
+ anthropic: "https://status.claude.com",
75
+ gemini: "https://status.cloud.google.com",
76
+ google: "https://status.cloud.google.com",
77
+ openai: "https://status.openai.com",
78
+ openrouter: "https://status.openrouter.ai"
79
+ };
80
+ var RETRYABLE_STATUSES = new Set([
81
+ 408,
82
+ 409,
83
+ 425,
84
+ 429,
85
+ 500,
86
+ 502,
87
+ 503,
88
+ 504,
89
+ 529
90
+ ]);
91
+ var CONNECTION_ERROR_PATTERNS = [
92
+ "econnreset",
93
+ "econnrefused",
94
+ "etimedout",
95
+ "enotfound",
96
+ "eai_again",
97
+ "socket hang up",
98
+ "fetch failed",
99
+ "network",
100
+ "terminated",
101
+ "timed out",
102
+ "timeout",
103
+ "and not retryable",
104
+ "no response body"
105
+ ];
106
+ var isAbortError = (err) => err instanceof Error && (err.name === "AbortError" || /\babort(ed)?\b/i.test(err.message));
107
+ var providerStatusPage = (provider) => PROVIDER_STATUS_PAGES[provider] ?? null;
108
+ var BILLING_ERROR_TYPES = new Set([
109
+ "credit_balance_exhausted",
110
+ "insufficient_quota",
111
+ "billing_hard_limit_reached",
112
+ "billing_not_active",
113
+ "insufficient_credits"
114
+ ]);
115
+
116
+ class ProviderError extends Error {
117
+ provider;
118
+ status;
119
+ type;
120
+ retryable;
121
+ billingUnavailable;
122
+ notSent;
123
+ metadata;
124
+ statusPageUrl;
125
+ constructor(init) {
126
+ super(init.message, init.cause === undefined ? undefined : { cause: init.cause });
127
+ this.name = "ProviderError";
128
+ this.provider = init.provider;
129
+ this.status = init.status ?? null;
130
+ this.type = init.type ?? (typeof init.metadata?.code === "string" ? init.metadata.code : typeof init.metadata?.type === "string" ? init.metadata.type : null);
131
+ 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));
132
+ this.notSent = init.notSent === true;
133
+ this.retryable = !this.billingUnavailable && init.retryable;
134
+ this.metadata = init.metadata;
135
+ this.statusPageUrl = providerStatusPage(init.provider);
136
+ }
137
+ static fromResponse(provider, status, body, type) {
138
+ let metadata;
139
+ try {
140
+ const parsed = JSON.parse(body);
141
+ const details = parsed?.error ?? parsed;
142
+ if (details && typeof details === "object" && !Array.isArray(details))
143
+ metadata = details;
144
+ } catch {}
145
+ return new ProviderError({
146
+ metadata,
147
+ message: `${capitalize(provider)} API error ${status}: ${body}`,
148
+ provider,
149
+ retryable: RETRYABLE_STATUSES.has(status),
150
+ status,
151
+ type: type ?? null
152
+ });
153
+ }
154
+ static from(err, provider) {
155
+ if (err instanceof ProviderError)
156
+ return err;
157
+ if (isProviderError(err))
158
+ return new ProviderError({
159
+ provider: err.provider,
160
+ message: err.message,
161
+ status: err.status,
162
+ type: err.type,
163
+ retryable: err.retryable,
164
+ notSent: err.notSent,
165
+ metadata: err.metadata,
166
+ cause: err
167
+ });
168
+ if (isAbortError(err))
169
+ throw err;
170
+ const rawMessage = err instanceof Error ? err.message : String(err ?? "");
171
+ const lower = rawMessage.toLowerCase();
172
+ const statusMatch = lower.match(/api error\s+(\d{3})/);
173
+ const status = statusMatch ? Number(statusMatch[1]) : null;
174
+ const retryable = status !== null && RETRYABLE_STATUSES.has(status) || status === null && CONNECTION_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
175
+ return new ProviderError({
176
+ cause: err,
177
+ message: rawMessage || `${capitalize(provider)} request failed`,
178
+ provider,
179
+ retryable,
180
+ status
181
+ });
182
+ }
183
+ }
184
+ var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
185
+ 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";
186
+
2
187
  // src/ai/inputCapacity.ts
3
188
  class AIInputError extends Error {
4
189
  code;
5
190
  providerStatus;
6
191
  requestId;
7
- constructor(code, message, providerStatus, requestId) {
192
+ providerError;
193
+ constructor(code, message, providerStatus, requestId, providerError) {
8
194
  super(message);
9
195
  this.code = code;
10
196
  this.providerStatus = providerStatus;
11
197
  this.requestId = requestId;
198
+ this.providerError = providerError;
12
199
  this.name = "AIInputError";
13
200
  }
14
201
  }
@@ -31,7 +218,7 @@ var inspectAIInput = async (provider, params) => {
31
218
  provider.inputCapacity.countTokens(params)
32
219
  ]).catch((error) => {
33
220
  if (error instanceof Error && error.name === "AIInputError" && "code" in error && (error.code === "capacity_unavailable" || error.code === "input_too_large" || error.code === "unsupported_content"))
34
- 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);
221
+ 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);
35
222
  throw error;
36
223
  });
37
224
  const inputTokens = inputTokenCount(tokens);
@@ -63,7 +250,7 @@ var cacheModelLimits = (load, ttlMs = 300000) => {
63
250
  return limits;
64
251
  };
65
252
  };
66
- var capacityJson = async (response) => {
253
+ var capacityJson = async (response, provider = "unknown") => {
67
254
  if (!response.ok) {
68
255
  const payload = await response.json().catch(() => {
69
256
  return;
@@ -74,7 +261,7 @@ var capacityJson = async (response) => {
74
261
  const message = nested?.message ?? record?.message;
75
262
  const detail = typeof message === "string" ? message.slice(0, 1000) : undefined;
76
263
  const id = response.headers.get("request-id") ?? response.headers.get("x-request-id") ?? record?.request_id;
77
- throw new AIInputError("capacity_unavailable", `Model capacity request failed (${response.status}).${detail ? ` ${detail}` : ""}`, response.status, typeof id === "string" ? id.slice(0, 200) : undefined);
264
+ 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 ?? {} })));
78
265
  }
79
266
  const value = await response.json();
80
267
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -304,113 +491,10 @@ var openaiInputCapacity = (config) => ({
304
491
  body: JSON.stringify(counted),
305
492
  signal: params.signal ? AbortSignal.any([params.signal, AbortSignal.timeout(15000)]) : AbortSignal.timeout(15000)
306
493
  });
307
- return inputTokenCount((await capacityJson(response)).input_tokens);
494
+ return inputTokenCount((await capacityJson(response, "openai")).input_tokens);
308
495
  }
309
496
  });
310
497
 
311
- // src/ai/errors/providerError.ts
312
- var PROVIDER_STATUS_PAGES = {
313
- anthropic: "https://status.claude.com",
314
- gemini: "https://status.cloud.google.com",
315
- google: "https://status.cloud.google.com",
316
- openai: "https://status.openai.com",
317
- openrouter: "https://status.openrouter.ai"
318
- };
319
- var RETRYABLE_STATUSES = new Set([
320
- 408,
321
- 409,
322
- 425,
323
- 429,
324
- 500,
325
- 502,
326
- 503,
327
- 504,
328
- 529
329
- ]);
330
- var CONNECTION_ERROR_PATTERNS = [
331
- "econnreset",
332
- "econnrefused",
333
- "etimedout",
334
- "enotfound",
335
- "eai_again",
336
- "socket hang up",
337
- "fetch failed",
338
- "network",
339
- "terminated",
340
- "timed out",
341
- "timeout",
342
- "and not retryable",
343
- "no response body"
344
- ];
345
- var isAbortError = (err) => err instanceof Error && (err.name === "AbortError" || /\babort(ed)?\b/i.test(err.message));
346
- var providerStatusPage = (provider) => PROVIDER_STATUS_PAGES[provider] ?? null;
347
- var BILLING_ERROR_TYPES = new Set([
348
- "credit_balance_exhausted",
349
- "insufficient_quota",
350
- "billing_hard_limit_reached",
351
- "billing_not_active",
352
- "insufficient_credits"
353
- ]);
354
-
355
- class ProviderError extends Error {
356
- provider;
357
- status;
358
- type;
359
- retryable;
360
- billingUnavailable;
361
- notSent;
362
- metadata;
363
- statusPageUrl;
364
- constructor(init) {
365
- super(init.message, init.cause === undefined ? undefined : { cause: init.cause });
366
- this.name = "ProviderError";
367
- this.provider = init.provider;
368
- this.status = init.status ?? null;
369
- this.type = init.type ?? (typeof init.metadata?.code === "string" ? init.metadata.code : typeof init.metadata?.type === "string" ? init.metadata.type : null);
370
- this.billingUnavailable = BILLING_ERROR_TYPES.has(this.type ?? "") || init.status === 402;
371
- this.notSent = init.notSent === true;
372
- this.retryable = !this.billingUnavailable && init.retryable;
373
- this.metadata = init.metadata;
374
- this.statusPageUrl = providerStatusPage(init.provider);
375
- }
376
- static fromResponse(provider, status, body, type) {
377
- let metadata;
378
- try {
379
- const parsed = JSON.parse(body);
380
- const details = parsed?.error ?? parsed;
381
- if (details && typeof details === "object" && !Array.isArray(details))
382
- metadata = details;
383
- } catch {}
384
- return new ProviderError({
385
- metadata,
386
- message: `${capitalize(provider)} API error ${status}: ${body}`,
387
- provider,
388
- retryable: RETRYABLE_STATUSES.has(status),
389
- status,
390
- type: type ?? null
391
- });
392
- }
393
- static from(err, provider) {
394
- if (err instanceof ProviderError)
395
- return err;
396
- if (isAbortError(err))
397
- throw err;
398
- const rawMessage = err instanceof Error ? err.message : String(err ?? "");
399
- const lower = rawMessage.toLowerCase();
400
- const statusMatch = lower.match(/api error\s+(\d{3})/);
401
- const status = statusMatch ? Number(statusMatch[1]) : null;
402
- const retryable = status !== null && RETRYABLE_STATUSES.has(status) || status === null && CONNECTION_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
403
- return new ProviderError({
404
- cause: err,
405
- message: rawMessage || `${capitalize(provider)} request failed`,
406
- provider,
407
- retryable,
408
- status
409
- });
410
- }
411
- }
412
- var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
413
-
414
498
  // src/ai/resilience.ts
415
499
  var DEFAULT_CONFIG = {
416
500
  baseDelayMs: 500,
@@ -540,6 +624,29 @@ var withResilience = (provider, providerName = "unknown") => {
540
624
  let billingFailure = null;
541
625
  let billingRetryAt = 0;
542
626
  let billingProbe = false;
627
+ const inspectAvailable = async (inspect) => {
628
+ if (billingFailure && Date.now() < billingRetryAt) {
629
+ throw new ProviderError({
630
+ provider: providerName,
631
+ message: "Provider billing is unavailable",
632
+ type: billingFailure.type,
633
+ status: billingFailure.status,
634
+ metadata: billingFailure.metadata,
635
+ retryable: false,
636
+ notSent: true
637
+ });
638
+ }
639
+ try {
640
+ return await inspect();
641
+ } catch (error) {
642
+ const nested = error instanceof Error && "providerError" in error ? error.providerError : error;
643
+ if (isProviderError(nested) && nested.billingUnavailable) {
644
+ billingFailure = ProviderError.from(nested, providerName);
645
+ billingRetryAt = Date.now() + billingCooldownMs;
646
+ }
647
+ throw error;
648
+ }
649
+ };
543
650
  const billingCooldownMs = 5 * 60000;
544
651
  const attempt = async function* (params, attemptNo) {
545
652
  let yielded = false;
@@ -567,33 +674,39 @@ var withResilience = (provider, providerName = "unknown") => {
567
674
  if (providerError.retryable)
568
675
  noteFailure(providerName, providerError);
569
676
  throw providerError;
677
+ } finally {
678
+ billingProbe = false;
570
679
  }
571
680
  };
572
681
  return {
573
682
  ...provider,
574
- stream: (params) => {
683
+ ...provider.inputCapacity ? {
684
+ inputCapacity: {
685
+ ...provider.inputCapacity,
686
+ getLimits: (params) => inspectAvailable(() => provider.inputCapacity.getLimits(params)),
687
+ countTokens: (params) => inspectAvailable(() => provider.inputCapacity.countTokens(params))
688
+ }
689
+ } : {},
690
+ stream: async function* (params) {
575
691
  if (billingFailure && (Date.now() < billingRetryAt || billingProbe)) {
576
692
  const failure = billingFailure;
577
- return async function* () {
578
- throw new ProviderError({
579
- provider: providerName,
580
- message: "Provider billing is unavailable",
581
- type: failure.type,
582
- status: failure.status,
583
- retryable: false,
584
- notSent: true
585
- });
586
- }();
693
+ throw new ProviderError({
694
+ provider: providerName,
695
+ message: "Provider billing is unavailable",
696
+ type: failure.type,
697
+ status: failure.status,
698
+ metadata: failure.metadata,
699
+ retryable: false,
700
+ notSent: true
701
+ });
587
702
  }
588
- if (billingFailure)
589
- billingProbe = true;
590
703
  const tripped = circuitOpen(providerName);
591
704
  if (tripped) {
592
- return async function* () {
593
- throw tripped;
594
- }();
705
+ throw tripped;
595
706
  }
596
- return attempt(params, 0);
707
+ if (billingFailure)
708
+ billingProbe = true;
709
+ yield* attempt(params, 0);
597
710
  }
598
711
  };
599
712
  };
@@ -602,7 +715,7 @@ var withResilience = (provider, providerName = "unknown") => {
602
715
  var instrumentAIProvider = (provider, providerName) => {
603
716
  const resilient = withResilience(provider, providerName);
604
717
  return {
605
- ...provider,
718
+ ...resilient,
606
719
  stream: (params) => {
607
720
  if (!params.onUsage && !params.onSpan) {
608
721
  return resilient.stream(params);
@@ -962,30 +1075,11 @@ var buildInput = (messages) => {
962
1075
  }
963
1076
  return input;
964
1077
  };
965
- var isStrictCompatible = (schema) => {
966
- if (Array.isArray(schema))
967
- return schema.every(isStrictCompatible);
968
- if (!isRecord(schema))
969
- return true;
970
- const { properties } = schema;
971
- if (isRecord(properties)) {
972
- const required = Array.isArray(schema.required) ? schema.required : [];
973
- if (schema.additionalProperties !== false)
974
- return false;
975
- if (!Object.keys(properties).every((key) => required.includes(key)))
976
- return false;
977
- }
978
- const nested = [schema.items, schema.anyOf];
979
- for (const map of [properties, schema.$defs, schema.definitions])
980
- if (isRecord(map))
981
- nested.push(...Object.values(map));
982
- return nested.every(isStrictCompatible);
983
- };
984
1078
  var mapToolDefinition = (tool) => ({
985
1079
  description: tool.description,
986
1080
  name: tool.name,
987
- parameters: tool.input_schema,
988
- strict: isStrictCompatible(tool.input_schema),
1081
+ parameters: openaiSchema(tool.input_schema),
1082
+ strict: isStrictCompatible(openaiSchema(tool.input_schema)),
989
1083
  type: "function"
990
1084
  });
991
1085
  var buildTools = (tools, isImageModel) => {
@@ -1042,7 +1136,7 @@ var buildResponsesRequestBody = (params, isImageModel, capabilityModel = params.
1042
1136
  body.text = {
1043
1137
  format: {
1044
1138
  name: params.responseFormat.name,
1045
- schema: params.responseFormat.schema,
1139
+ schema: openaiSchema(params.responseFormat.schema),
1046
1140
  strict: params.responseFormat.strict ?? true,
1047
1141
  type: "json_schema"
1048
1142
  }
@@ -1509,7 +1603,7 @@ var mapToolDefinitions = (tools) => tools.map((tool) => ({
1509
1603
  function: {
1510
1604
  description: tool.description,
1511
1605
  name: tool.name,
1512
- parameters: tool.input_schema
1606
+ parameters: openaiSchema(tool.input_schema)
1513
1607
  },
1514
1608
  type: "function"
1515
1609
  }));
@@ -1635,7 +1729,7 @@ var buildRequestBody = (params, capabilityModel = params.model, providerName = "
1635
1729
  body.response_format = {
1636
1730
  json_schema: {
1637
1731
  name: params.responseFormat.name,
1638
- schema: params.responseFormat.schema,
1732
+ schema: openaiSchema(params.responseFormat.schema),
1639
1733
  strict: params.responseFormat.strict ?? true
1640
1734
  },
1641
1735
  type: "json_schema"
@@ -2028,5 +2122,5 @@ export {
2028
2122
  openai
2029
2123
  };
2030
2124
 
2031
- //# debugId=0CC011402536F35664756E2164756E21
2125
+ //# debugId=E5102C547733AF4E64756E2164756E21
2032
2126
  //# sourceMappingURL=openai.js.map