@absolutejs/ai 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +18 -0
- package/changelog.json +37 -0
- package/dist/ai/index.js +359 -188
- package/dist/ai/index.js.map +17 -16
- package/dist/ai/providers/anthropic.js +147 -104
- package/dist/ai/providers/anthropic.js.map +8 -8
- package/dist/ai/providers/gemini.js +146 -103
- package/dist/ai/providers/gemini.js.map +7 -7
- package/dist/ai/providers/ollama.js +61 -20
- package/dist/ai/providers/ollama.js.map +5 -5
- package/dist/ai/providers/openai.js +245 -151
- package/dist/ai/providers/openai.js.map +11 -10
- package/dist/ai/providers/openaiCompatible.js +245 -151
- package/dist/ai/providers/openaiCompatible.js.map +11 -10
- package/dist/ai/providers/openaiResponses.js +243 -149
- package/dist/ai/providers/openaiResponses.js.map +10 -9
- package/dist/ai/providers/openrouter.js +246 -152
- package/dist/ai/providers/openrouter.js.map +12 -11
- package/dist/src/ai/contextPolicy.d.ts +2 -0
- package/dist/src/ai/errors/providerError.d.ts +2 -0
- package/dist/src/ai/index.d.ts +3 -0
- package/dist/src/ai/inputCapacity.d.ts +4 -2
- package/dist/src/ai/prepareTextInput.d.ts +2 -0
- package/dist/src/ai/providerFallback.d.ts +27 -8
- package/dist/src/ai/providers/openaiSchema.d.ts +4 -0
- package/package.json +3 -3
|
@@ -1,87 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// src/ai/inputCapacity.ts
|
|
3
|
-
class AIInputError extends Error {
|
|
4
|
-
code;
|
|
5
|
-
providerStatus;
|
|
6
|
-
requestId;
|
|
7
|
-
constructor(code, message, providerStatus, requestId) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.code = code;
|
|
10
|
-
this.providerStatus = providerStatus;
|
|
11
|
-
this.requestId = requestId;
|
|
12
|
-
this.name = "AIInputError";
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
var positiveTokenLimit = (value) => {
|
|
16
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)
|
|
17
|
-
throw new AIInputError("capacity_unavailable", "The provider did not supply a valid model limit.");
|
|
18
|
-
return value;
|
|
19
|
-
};
|
|
20
|
-
var inputTokenCount = (value) => {
|
|
21
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
22
|
-
throw new AIInputError("capacity_unavailable", "The provider did not supply a valid token count.");
|
|
23
|
-
return value;
|
|
24
|
-
};
|
|
25
|
-
var inspectAIInput = async (provider, params) => {
|
|
26
|
-
params.signal?.throwIfAborted();
|
|
27
|
-
if (!provider.inputCapacity)
|
|
28
|
-
throw new AIInputError("capacity_unavailable", "This provider does not expose model capacity and token counting.");
|
|
29
|
-
const [limits, tokens] = await Promise.all([
|
|
30
|
-
provider.inputCapacity.getLimits(params),
|
|
31
|
-
provider.inputCapacity.countTokens(params)
|
|
32
|
-
]).catch((error) => {
|
|
33
|
-
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);
|
|
35
|
-
throw error;
|
|
36
|
-
});
|
|
37
|
-
const inputTokens = inputTokenCount(tokens);
|
|
38
|
-
const outputTokens = positiveTokenLimit(provider.inputCapacity.outputTokens?.(params) ?? params.maxTokens ?? limits.maxOutputTokens);
|
|
39
|
-
const maxInputTokens = positiveTokenLimit(limits.maxInputTokens);
|
|
40
|
-
const maxOutputTokens = positiveTokenLimit(limits.maxOutputTokens);
|
|
41
|
-
const availableInputTokens = Math.max(0, Math.min(maxInputTokens, limits.contextWindowTokens === undefined ? maxInputTokens : positiveTokenLimit(limits.contextWindowTokens) - outputTokens));
|
|
42
|
-
return {
|
|
43
|
-
limits,
|
|
44
|
-
inputTokens,
|
|
45
|
-
inputTokenScope: provider.inputCapacity.countScope?.(params) ?? "request",
|
|
46
|
-
outputTokens,
|
|
47
|
-
availableInputTokens,
|
|
48
|
-
fits: outputTokens <= maxOutputTokens && inputTokens <= availableInputTokens
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
var cacheModelLimits = (load, ttlMs = 300000) => {
|
|
52
|
-
const cache = new Map;
|
|
53
|
-
return async (params) => {
|
|
54
|
-
const entry = cache.get(params.model);
|
|
55
|
-
if (entry && Date.now() - entry.at < ttlMs)
|
|
56
|
-
return entry.limits;
|
|
57
|
-
const limits = await load(params);
|
|
58
|
-
positiveTokenLimit(limits.maxInputTokens);
|
|
59
|
-
positiveTokenLimit(limits.maxOutputTokens);
|
|
60
|
-
if (cache.size >= 100)
|
|
61
|
-
cache.clear();
|
|
62
|
-
cache.set(params.model, { at: Date.now(), limits });
|
|
63
|
-
return limits;
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
var capacityJson = async (response) => {
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
const payload = await response.json().catch(() => {
|
|
69
|
-
return;
|
|
70
|
-
});
|
|
71
|
-
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : undefined;
|
|
72
|
-
const providerError = record?.error;
|
|
73
|
-
const nested = providerError && typeof providerError === "object" && !Array.isArray(providerError) ? providerError : undefined;
|
|
74
|
-
const message = nested?.message ?? record?.message;
|
|
75
|
-
const detail = typeof message === "string" ? message.slice(0, 1000) : undefined;
|
|
76
|
-
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);
|
|
78
|
-
}
|
|
79
|
-
const value = await response.json();
|
|
80
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
81
|
-
throw new AIInputError("capacity_unavailable", "Model capacity response was invalid.");
|
|
82
|
-
return value;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
2
|
// src/ai/errors/providerError.ts
|
|
86
3
|
var PROVIDER_STATUS_PAGES = {
|
|
87
4
|
anthropic: "https://status.claude.com",
|
|
@@ -141,7 +58,7 @@ class ProviderError extends Error {
|
|
|
141
58
|
this.provider = init.provider;
|
|
142
59
|
this.status = init.status ?? null;
|
|
143
60
|
this.type = init.type ?? (typeof init.metadata?.code === "string" ? init.metadata.code : typeof init.metadata?.type === "string" ? init.metadata.type : null);
|
|
144
|
-
this.billingUnavailable = BILLING_ERROR_TYPES.has(this.type ?? "") || init.status === 402;
|
|
61
|
+
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));
|
|
145
62
|
this.notSent = init.notSent === true;
|
|
146
63
|
this.retryable = !this.billingUnavailable && init.retryable;
|
|
147
64
|
this.metadata = init.metadata;
|
|
@@ -167,6 +84,17 @@ class ProviderError extends Error {
|
|
|
167
84
|
static from(err, provider) {
|
|
168
85
|
if (err instanceof ProviderError)
|
|
169
86
|
return err;
|
|
87
|
+
if (isProviderError(err))
|
|
88
|
+
return new ProviderError({
|
|
89
|
+
provider: err.provider,
|
|
90
|
+
message: err.message,
|
|
91
|
+
status: err.status,
|
|
92
|
+
type: err.type,
|
|
93
|
+
retryable: err.retryable,
|
|
94
|
+
notSent: err.notSent,
|
|
95
|
+
metadata: err.metadata,
|
|
96
|
+
cause: err
|
|
97
|
+
});
|
|
170
98
|
if (isAbortError(err))
|
|
171
99
|
throw err;
|
|
172
100
|
const rawMessage = err instanceof Error ? err.message : String(err ?? "");
|
|
@@ -184,6 +112,92 @@ class ProviderError extends Error {
|
|
|
184
112
|
}
|
|
185
113
|
}
|
|
186
114
|
var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
115
|
+
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";
|
|
116
|
+
|
|
117
|
+
// src/ai/inputCapacity.ts
|
|
118
|
+
class AIInputError extends Error {
|
|
119
|
+
code;
|
|
120
|
+
providerStatus;
|
|
121
|
+
requestId;
|
|
122
|
+
providerError;
|
|
123
|
+
constructor(code, message, providerStatus, requestId, providerError) {
|
|
124
|
+
super(message);
|
|
125
|
+
this.code = code;
|
|
126
|
+
this.providerStatus = providerStatus;
|
|
127
|
+
this.requestId = requestId;
|
|
128
|
+
this.providerError = providerError;
|
|
129
|
+
this.name = "AIInputError";
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
var positiveTokenLimit = (value) => {
|
|
133
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)
|
|
134
|
+
throw new AIInputError("capacity_unavailable", "The provider did not supply a valid model limit.");
|
|
135
|
+
return value;
|
|
136
|
+
};
|
|
137
|
+
var inputTokenCount = (value) => {
|
|
138
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
139
|
+
throw new AIInputError("capacity_unavailable", "The provider did not supply a valid token count.");
|
|
140
|
+
return value;
|
|
141
|
+
};
|
|
142
|
+
var inspectAIInput = async (provider, params) => {
|
|
143
|
+
params.signal?.throwIfAborted();
|
|
144
|
+
if (!provider.inputCapacity)
|
|
145
|
+
throw new AIInputError("capacity_unavailable", "This provider does not expose model capacity and token counting.");
|
|
146
|
+
const [limits, tokens] = await Promise.all([
|
|
147
|
+
provider.inputCapacity.getLimits(params),
|
|
148
|
+
provider.inputCapacity.countTokens(params)
|
|
149
|
+
]).catch((error) => {
|
|
150
|
+
if (error instanceof Error && error.name === "AIInputError" && "code" in error && (error.code === "capacity_unavailable" || error.code === "input_too_large" || error.code === "unsupported_content"))
|
|
151
|
+
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);
|
|
152
|
+
throw error;
|
|
153
|
+
});
|
|
154
|
+
const inputTokens = inputTokenCount(tokens);
|
|
155
|
+
const outputTokens = positiveTokenLimit(provider.inputCapacity.outputTokens?.(params) ?? params.maxTokens ?? limits.maxOutputTokens);
|
|
156
|
+
const maxInputTokens = positiveTokenLimit(limits.maxInputTokens);
|
|
157
|
+
const maxOutputTokens = positiveTokenLimit(limits.maxOutputTokens);
|
|
158
|
+
const availableInputTokens = Math.max(0, Math.min(maxInputTokens, limits.contextWindowTokens === undefined ? maxInputTokens : positiveTokenLimit(limits.contextWindowTokens) - outputTokens));
|
|
159
|
+
return {
|
|
160
|
+
limits,
|
|
161
|
+
inputTokens,
|
|
162
|
+
inputTokenScope: provider.inputCapacity.countScope?.(params) ?? "request",
|
|
163
|
+
outputTokens,
|
|
164
|
+
availableInputTokens,
|
|
165
|
+
fits: outputTokens <= maxOutputTokens && inputTokens <= availableInputTokens
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
var cacheModelLimits = (load, ttlMs = 300000) => {
|
|
169
|
+
const cache = new Map;
|
|
170
|
+
return async (params) => {
|
|
171
|
+
const entry = cache.get(params.model);
|
|
172
|
+
if (entry && Date.now() - entry.at < ttlMs)
|
|
173
|
+
return entry.limits;
|
|
174
|
+
const limits = await load(params);
|
|
175
|
+
positiveTokenLimit(limits.maxInputTokens);
|
|
176
|
+
positiveTokenLimit(limits.maxOutputTokens);
|
|
177
|
+
if (cache.size >= 100)
|
|
178
|
+
cache.clear();
|
|
179
|
+
cache.set(params.model, { at: Date.now(), limits });
|
|
180
|
+
return limits;
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
var capacityJson = async (response, provider = "unknown") => {
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
const payload = await response.json().catch(() => {
|
|
186
|
+
return;
|
|
187
|
+
});
|
|
188
|
+
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : undefined;
|
|
189
|
+
const providerError = record?.error;
|
|
190
|
+
const nested = providerError && typeof providerError === "object" && !Array.isArray(providerError) ? providerError : undefined;
|
|
191
|
+
const message = nested?.message ?? record?.message;
|
|
192
|
+
const detail = typeof message === "string" ? message.slice(0, 1000) : undefined;
|
|
193
|
+
const id = response.headers.get("request-id") ?? response.headers.get("x-request-id") ?? record?.request_id;
|
|
194
|
+
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 ?? {} })));
|
|
195
|
+
}
|
|
196
|
+
const value = await response.json();
|
|
197
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
198
|
+
throw new AIInputError("capacity_unavailable", "Model capacity response was invalid.");
|
|
199
|
+
return value;
|
|
200
|
+
};
|
|
187
201
|
|
|
188
202
|
// src/ai/resilience.ts
|
|
189
203
|
var DEFAULT_CONFIG = {
|
|
@@ -314,6 +328,29 @@ var withResilience = (provider, providerName = "unknown") => {
|
|
|
314
328
|
let billingFailure = null;
|
|
315
329
|
let billingRetryAt = 0;
|
|
316
330
|
let billingProbe = false;
|
|
331
|
+
const inspectAvailable = async (inspect) => {
|
|
332
|
+
if (billingFailure && Date.now() < billingRetryAt) {
|
|
333
|
+
throw new ProviderError({
|
|
334
|
+
provider: providerName,
|
|
335
|
+
message: "Provider billing is unavailable",
|
|
336
|
+
type: billingFailure.type,
|
|
337
|
+
status: billingFailure.status,
|
|
338
|
+
metadata: billingFailure.metadata,
|
|
339
|
+
retryable: false,
|
|
340
|
+
notSent: true
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
return await inspect();
|
|
345
|
+
} catch (error) {
|
|
346
|
+
const nested = error instanceof Error && "providerError" in error ? error.providerError : error;
|
|
347
|
+
if (isProviderError(nested) && nested.billingUnavailable) {
|
|
348
|
+
billingFailure = ProviderError.from(nested, providerName);
|
|
349
|
+
billingRetryAt = Date.now() + billingCooldownMs;
|
|
350
|
+
}
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
317
354
|
const billingCooldownMs = 5 * 60000;
|
|
318
355
|
const attempt = async function* (params, attemptNo) {
|
|
319
356
|
let yielded = false;
|
|
@@ -341,33 +378,39 @@ var withResilience = (provider, providerName = "unknown") => {
|
|
|
341
378
|
if (providerError.retryable)
|
|
342
379
|
noteFailure(providerName, providerError);
|
|
343
380
|
throw providerError;
|
|
381
|
+
} finally {
|
|
382
|
+
billingProbe = false;
|
|
344
383
|
}
|
|
345
384
|
};
|
|
346
385
|
return {
|
|
347
386
|
...provider,
|
|
348
|
-
|
|
387
|
+
...provider.inputCapacity ? {
|
|
388
|
+
inputCapacity: {
|
|
389
|
+
...provider.inputCapacity,
|
|
390
|
+
getLimits: (params) => inspectAvailable(() => provider.inputCapacity.getLimits(params)),
|
|
391
|
+
countTokens: (params) => inspectAvailable(() => provider.inputCapacity.countTokens(params))
|
|
392
|
+
}
|
|
393
|
+
} : {},
|
|
394
|
+
stream: async function* (params) {
|
|
349
395
|
if (billingFailure && (Date.now() < billingRetryAt || billingProbe)) {
|
|
350
396
|
const failure = billingFailure;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}();
|
|
397
|
+
throw new ProviderError({
|
|
398
|
+
provider: providerName,
|
|
399
|
+
message: "Provider billing is unavailable",
|
|
400
|
+
type: failure.type,
|
|
401
|
+
status: failure.status,
|
|
402
|
+
metadata: failure.metadata,
|
|
403
|
+
retryable: false,
|
|
404
|
+
notSent: true
|
|
405
|
+
});
|
|
361
406
|
}
|
|
362
|
-
if (billingFailure)
|
|
363
|
-
billingProbe = true;
|
|
364
407
|
const tripped = circuitOpen(providerName);
|
|
365
408
|
if (tripped) {
|
|
366
|
-
|
|
367
|
-
throw tripped;
|
|
368
|
-
}();
|
|
409
|
+
throw tripped;
|
|
369
410
|
}
|
|
370
|
-
|
|
411
|
+
if (billingFailure)
|
|
412
|
+
billingProbe = true;
|
|
413
|
+
yield* attempt(params, 0);
|
|
371
414
|
}
|
|
372
415
|
};
|
|
373
416
|
};
|
|
@@ -376,7 +419,7 @@ var withResilience = (provider, providerName = "unknown") => {
|
|
|
376
419
|
var instrumentAIProvider = (provider, providerName) => {
|
|
377
420
|
const resilient = withResilience(provider, providerName);
|
|
378
421
|
return {
|
|
379
|
-
...
|
|
422
|
+
...resilient,
|
|
380
423
|
stream: (params) => {
|
|
381
424
|
if (!params.onUsage && !params.onSpan) {
|
|
382
425
|
return resilient.stream(params);
|
|
@@ -1366,7 +1409,7 @@ var anthropic = (config) => {
|
|
|
1366
1409
|
headers,
|
|
1367
1410
|
body: body ? JSON.stringify(body) : undefined,
|
|
1368
1411
|
signal: params.signal ? AbortSignal.any([params.signal, AbortSignal.timeout(15000)]) : AbortSignal.timeout(15000)
|
|
1369
|
-
}));
|
|
1412
|
+
}), "anthropic");
|
|
1370
1413
|
};
|
|
1371
1414
|
const countingRequest = (params) => {
|
|
1372
1415
|
const built = buildRequestBody(params, configuredMax, promptCaching);
|
|
@@ -1433,5 +1476,5 @@ export {
|
|
|
1433
1476
|
anthropic
|
|
1434
1477
|
};
|
|
1435
1478
|
|
|
1436
|
-
//# debugId=
|
|
1479
|
+
//# debugId=E6E9FD6C4A0FEEBF64756E2164756E21
|
|
1437
1480
|
//# sourceMappingURL=anthropic.js.map
|