@absolutejs/ai 0.0.21 → 0.0.22
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 +341 -99
- package/dist/ai/index.js.map +8 -6
- package/dist/ai/providers/anthropic.js +248 -16
- package/dist/ai/providers/anthropic.js.map +7 -5
- package/dist/ai/providers/gemini.js +233 -12
- package/dist/ai/providers/gemini.js.map +6 -4
- package/dist/ai/providers/ollama.js +231 -10
- package/dist/ai/providers/ollama.js.map +6 -4
- package/dist/ai/providers/openai.js +241 -16
- package/dist/ai/providers/openai.js.map +7 -5
- package/dist/ai/providers/openaiCompatible.js +259 -34
- package/dist/ai/providers/openaiCompatible.js.map +7 -5
- package/dist/ai/providers/openaiResponses.js +233 -12
- package/dist/ai/providers/openaiResponses.js.map +6 -4
- package/dist/src/ai/errors/providerError.d.ts +33 -0
- package/dist/src/ai/index.d.ts +4 -0
- package/dist/src/ai/resilience.d.ts +43 -0
- package/package.json +1 -1
package/dist/ai/index.js
CHANGED
|
@@ -1,15 +1,236 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __require = import.meta.require;
|
|
3
3
|
|
|
4
|
-
// src/ai/
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
// src/ai/errors/providerError.ts
|
|
5
|
+
var PROVIDER_STATUS_PAGES = {
|
|
6
|
+
anthropic: "https://status.anthropic.com",
|
|
7
|
+
gemini: "https://status.cloud.google.com",
|
|
8
|
+
google: "https://status.cloud.google.com",
|
|
9
|
+
openai: "https://status.openai.com"
|
|
10
|
+
};
|
|
11
|
+
var RETRYABLE_STATUSES = new Set([
|
|
12
|
+
408,
|
|
13
|
+
409,
|
|
14
|
+
425,
|
|
15
|
+
429,
|
|
16
|
+
500,
|
|
17
|
+
502,
|
|
18
|
+
503,
|
|
19
|
+
504,
|
|
20
|
+
529
|
|
21
|
+
]);
|
|
22
|
+
var CONNECTION_ERROR_PATTERNS = [
|
|
23
|
+
"econnreset",
|
|
24
|
+
"econnrefused",
|
|
25
|
+
"etimedout",
|
|
26
|
+
"enotfound",
|
|
27
|
+
"eai_again",
|
|
28
|
+
"socket hang up",
|
|
29
|
+
"fetch failed",
|
|
30
|
+
"network",
|
|
31
|
+
"terminated",
|
|
32
|
+
"timed out",
|
|
33
|
+
"timeout",
|
|
34
|
+
"and not retryable",
|
|
35
|
+
"no response body"
|
|
36
|
+
];
|
|
37
|
+
var isAbortError = (err) => err instanceof Error && (err.name === "AbortError" || /\babort(ed)?\b/i.test(err.message));
|
|
38
|
+
var providerStatusPage = (provider) => PROVIDER_STATUS_PAGES[provider] ?? null;
|
|
39
|
+
|
|
40
|
+
class ProviderError extends Error {
|
|
41
|
+
provider;
|
|
42
|
+
status;
|
|
43
|
+
type;
|
|
44
|
+
retryable;
|
|
45
|
+
statusPageUrl;
|
|
46
|
+
constructor(init) {
|
|
47
|
+
super(init.message, init.cause === undefined ? undefined : { cause: init.cause });
|
|
48
|
+
this.name = "ProviderError";
|
|
49
|
+
this.provider = init.provider;
|
|
50
|
+
this.status = init.status ?? null;
|
|
51
|
+
this.type = init.type ?? null;
|
|
52
|
+
this.retryable = init.retryable;
|
|
53
|
+
this.statusPageUrl = providerStatusPage(init.provider);
|
|
54
|
+
}
|
|
55
|
+
static fromResponse(provider, status, body, type) {
|
|
56
|
+
return new ProviderError({
|
|
57
|
+
message: `${capitalize(provider)} API error ${status}: ${body}`,
|
|
58
|
+
provider,
|
|
59
|
+
retryable: RETRYABLE_STATUSES.has(status),
|
|
60
|
+
status,
|
|
61
|
+
type: type ?? null
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
static from(err, provider) {
|
|
65
|
+
if (err instanceof ProviderError)
|
|
66
|
+
return err;
|
|
67
|
+
if (isAbortError(err))
|
|
68
|
+
throw err;
|
|
69
|
+
const rawMessage = err instanceof Error ? err.message : String(err ?? "");
|
|
70
|
+
const lower = rawMessage.toLowerCase();
|
|
71
|
+
const statusMatch = lower.match(/api error\s+(\d{3})/);
|
|
72
|
+
const status = statusMatch ? Number(statusMatch[1]) : null;
|
|
73
|
+
const retryable = status !== null && RETRYABLE_STATUSES.has(status) || status === null && CONNECTION_ERROR_PATTERNS.some((pattern) => lower.includes(pattern));
|
|
74
|
+
return new ProviderError({
|
|
75
|
+
cause: err,
|
|
76
|
+
message: rawMessage || `${capitalize(provider)} request failed`,
|
|
77
|
+
provider,
|
|
78
|
+
retryable,
|
|
79
|
+
status
|
|
80
|
+
});
|
|
11
81
|
}
|
|
82
|
+
}
|
|
83
|
+
var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
84
|
+
|
|
85
|
+
// src/ai/resilience.ts
|
|
86
|
+
var DEFAULT_CONFIG = {
|
|
87
|
+
baseDelayMs: 500,
|
|
88
|
+
failureThreshold: 4,
|
|
89
|
+
maxDelayMs: 8000,
|
|
90
|
+
maxRetries: 2,
|
|
91
|
+
openMs: 30000
|
|
92
|
+
};
|
|
93
|
+
var config = { ...DEFAULT_CONFIG };
|
|
94
|
+
var configureProviderResilience = (partial) => {
|
|
95
|
+
config = { ...config, ...partial };
|
|
96
|
+
};
|
|
97
|
+
var health = new Map;
|
|
98
|
+
var recordFor = (provider) => {
|
|
99
|
+
const existing = health.get(provider);
|
|
100
|
+
if (existing)
|
|
101
|
+
return existing;
|
|
102
|
+
const fresh = {
|
|
103
|
+
consecutiveFailures: 0,
|
|
104
|
+
lastError: null,
|
|
105
|
+
lastFailureAt: null,
|
|
106
|
+
lastSuccessAt: null,
|
|
107
|
+
nextProbeAt: null,
|
|
108
|
+
state: "closed"
|
|
109
|
+
};
|
|
110
|
+
health.set(provider, fresh);
|
|
111
|
+
return fresh;
|
|
112
|
+
};
|
|
113
|
+
var noteSuccess = (provider) => {
|
|
114
|
+
const record = recordFor(provider);
|
|
115
|
+
record.state = "closed";
|
|
116
|
+
record.consecutiveFailures = 0;
|
|
117
|
+
record.nextProbeAt = null;
|
|
118
|
+
record.lastError = null;
|
|
119
|
+
record.lastSuccessAt = Date.now();
|
|
120
|
+
};
|
|
121
|
+
var noteFailure = (provider, error) => {
|
|
122
|
+
const record = recordFor(provider);
|
|
123
|
+
record.consecutiveFailures += 1;
|
|
124
|
+
record.lastFailureAt = Date.now();
|
|
125
|
+
record.lastError = {
|
|
126
|
+
message: error.message,
|
|
127
|
+
status: error.status,
|
|
128
|
+
type: error.type
|
|
129
|
+
};
|
|
130
|
+
if (record.consecutiveFailures >= config.failureThreshold) {
|
|
131
|
+
record.state = "open";
|
|
132
|
+
record.nextProbeAt = Date.now() + config.openMs;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
var snapshot = (provider, record) => ({
|
|
136
|
+
consecutiveFailures: record.consecutiveFailures,
|
|
137
|
+
healthy: record.state === "closed",
|
|
138
|
+
lastError: record.lastError,
|
|
139
|
+
lastFailureAt: record.lastFailureAt,
|
|
140
|
+
lastSuccessAt: record.lastSuccessAt,
|
|
141
|
+
nextRetryAt: record.nextProbeAt,
|
|
142
|
+
provider,
|
|
143
|
+
state: record.state,
|
|
144
|
+
statusPageUrl: providerStatusPage(provider)
|
|
12
145
|
});
|
|
146
|
+
function getProviderHealth(provider) {
|
|
147
|
+
if (provider !== undefined)
|
|
148
|
+
return snapshot(provider, recordFor(provider));
|
|
149
|
+
return [...health.entries()].map(([name, record]) => snapshot(name, record));
|
|
150
|
+
}
|
|
151
|
+
var backoffDelay = (attempt) => {
|
|
152
|
+
const exponential = config.baseDelayMs * 2 ** attempt;
|
|
153
|
+
const capped = Math.min(exponential, config.maxDelayMs);
|
|
154
|
+
return Math.round(capped / 2 + Math.random() * capped / 2);
|
|
155
|
+
};
|
|
156
|
+
var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
157
|
+
if (signal?.aborted) {
|
|
158
|
+
reject(signal.reason ?? new Error("aborted"));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const timer = setTimeout(() => {
|
|
162
|
+
signal?.removeEventListener("abort", onAbort);
|
|
163
|
+
resolve();
|
|
164
|
+
}, ms);
|
|
165
|
+
const onAbort = () => {
|
|
166
|
+
clearTimeout(timer);
|
|
167
|
+
reject(signal?.reason ?? new Error("aborted"));
|
|
168
|
+
};
|
|
169
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
170
|
+
});
|
|
171
|
+
var circuitOpen = (provider) => {
|
|
172
|
+
const record = recordFor(provider);
|
|
173
|
+
if (record.state !== "open")
|
|
174
|
+
return null;
|
|
175
|
+
if (record.nextProbeAt !== null && Date.now() >= record.nextProbeAt) {
|
|
176
|
+
record.state = "half-open";
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const detail = record.lastError?.message ?? "recent failures";
|
|
180
|
+
return new ProviderError({
|
|
181
|
+
message: `${provider} is temporarily unavailable (circuit open after ${record.consecutiveFailures} failures): ${detail}`,
|
|
182
|
+
provider,
|
|
183
|
+
retryable: true,
|
|
184
|
+
status: record.lastError?.status ?? null,
|
|
185
|
+
type: record.lastError?.type ?? null
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
var withResilience = (provider, providerName = "unknown") => {
|
|
189
|
+
const attempt = async function* (params, attemptNo) {
|
|
190
|
+
let yielded = false;
|
|
191
|
+
try {
|
|
192
|
+
for await (const chunk of provider.stream(params)) {
|
|
193
|
+
yielded = true;
|
|
194
|
+
yield chunk;
|
|
195
|
+
}
|
|
196
|
+
noteSuccess(providerName);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
const providerError = ProviderError.from(err, providerName);
|
|
199
|
+
const canRetry = !yielded && providerError.retryable && attemptNo < config.maxRetries && !params.signal?.aborted;
|
|
200
|
+
if (canRetry) {
|
|
201
|
+
await sleep(backoffDelay(attemptNo), params.signal);
|
|
202
|
+
yield* attempt(params, attemptNo + 1);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
noteFailure(providerName, providerError);
|
|
206
|
+
throw providerError;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
return {
|
|
210
|
+
stream: (params) => {
|
|
211
|
+
const tripped = circuitOpen(providerName);
|
|
212
|
+
if (tripped) {
|
|
213
|
+
return async function* () {
|
|
214
|
+
throw tripped;
|
|
215
|
+
}();
|
|
216
|
+
}
|
|
217
|
+
return attempt(params, 0);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// src/ai/providers/instrumentation.ts
|
|
223
|
+
var instrumentAIProvider = (provider, providerName) => {
|
|
224
|
+
const resilient = withResilience(provider, providerName);
|
|
225
|
+
return {
|
|
226
|
+
stream: (params) => {
|
|
227
|
+
if (!params.onUsage && !params.onSpan) {
|
|
228
|
+
return resilient.stream(params);
|
|
229
|
+
}
|
|
230
|
+
return tapStream(resilient.stream(params), params, providerName);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
};
|
|
13
234
|
async function* tapStream(source, params, providerName) {
|
|
14
235
|
const startedAt = Date.now();
|
|
15
236
|
let lastUsage;
|
|
@@ -501,23 +722,27 @@ var fetchOpenAIStream = async function* (baseUrl, apiKey, body, signal) {
|
|
|
501
722
|
});
|
|
502
723
|
if (!response.ok) {
|
|
503
724
|
const errorText = await response.text();
|
|
504
|
-
throw
|
|
725
|
+
throw ProviderError.fromResponse("openai", response.status, errorText);
|
|
505
726
|
}
|
|
506
727
|
if (!response.body) {
|
|
507
|
-
throw new
|
|
728
|
+
throw new ProviderError({
|
|
729
|
+
message: "OpenAI API returned no response body",
|
|
730
|
+
provider: "openai",
|
|
731
|
+
retryable: true
|
|
732
|
+
});
|
|
508
733
|
}
|
|
509
734
|
yield* parseSSEStream(response.body, signal);
|
|
510
735
|
};
|
|
511
|
-
var openai = (
|
|
512
|
-
const baseUrl =
|
|
513
|
-
if (!
|
|
736
|
+
var openai = (config2) => {
|
|
737
|
+
const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL;
|
|
738
|
+
if (!config2.apiKey && !config2.tokenSource) {
|
|
514
739
|
throw new Error("openai() requires either apiKey or tokenSource");
|
|
515
740
|
}
|
|
516
741
|
const resolveKey = async () => {
|
|
517
|
-
if (
|
|
518
|
-
return await Promise.resolve(
|
|
742
|
+
if (config2.tokenSource) {
|
|
743
|
+
return await Promise.resolve(config2.tokenSource());
|
|
519
744
|
}
|
|
520
|
-
return
|
|
745
|
+
return config2.apiKey;
|
|
521
746
|
};
|
|
522
747
|
return instrumentAIProvider({
|
|
523
748
|
stream: (params) => {
|
|
@@ -531,37 +756,37 @@ var openai = (config) => {
|
|
|
531
756
|
};
|
|
532
757
|
|
|
533
758
|
// src/ai/providers/openaiCompatible.ts
|
|
534
|
-
var alibaba = (
|
|
535
|
-
apiKey:
|
|
759
|
+
var alibaba = (config2) => openaiCompatible({
|
|
760
|
+
apiKey: config2.apiKey,
|
|
536
761
|
baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode"
|
|
537
762
|
});
|
|
538
|
-
var deepseek = (
|
|
539
|
-
apiKey:
|
|
763
|
+
var deepseek = (config2) => openaiCompatible({
|
|
764
|
+
apiKey: config2.apiKey,
|
|
540
765
|
baseUrl: "https://api.deepseek.com"
|
|
541
766
|
});
|
|
542
|
-
var google = (
|
|
543
|
-
apiKey:
|
|
767
|
+
var google = (config2) => openaiCompatible({
|
|
768
|
+
apiKey: config2.apiKey,
|
|
544
769
|
baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai"
|
|
545
770
|
});
|
|
546
|
-
var meta = (
|
|
547
|
-
apiKey:
|
|
771
|
+
var meta = (config2) => openaiCompatible({
|
|
772
|
+
apiKey: config2.apiKey,
|
|
548
773
|
baseUrl: "https://api.llama.com/compat/v1"
|
|
549
774
|
});
|
|
550
|
-
var mistralai = (
|
|
551
|
-
apiKey:
|
|
775
|
+
var mistralai = (config2) => openaiCompatible({
|
|
776
|
+
apiKey: config2.apiKey,
|
|
552
777
|
baseUrl: "https://api.mistral.ai"
|
|
553
778
|
});
|
|
554
|
-
var moonshot = (
|
|
555
|
-
apiKey:
|
|
779
|
+
var moonshot = (config2) => openaiCompatible({
|
|
780
|
+
apiKey: config2.apiKey,
|
|
556
781
|
baseUrl: "https://api.moonshot.ai"
|
|
557
782
|
});
|
|
558
|
-
var openaiCompatible = (
|
|
559
|
-
apiKey:
|
|
560
|
-
baseUrl:
|
|
561
|
-
tokenSource:
|
|
783
|
+
var openaiCompatible = (config2) => openai({
|
|
784
|
+
apiKey: config2.apiKey,
|
|
785
|
+
baseUrl: config2.baseUrl,
|
|
786
|
+
tokenSource: config2.tokenSource
|
|
562
787
|
});
|
|
563
|
-
var xai = (
|
|
564
|
-
apiKey:
|
|
788
|
+
var xai = (config2) => openaiCompatible({
|
|
789
|
+
apiKey: config2.apiKey,
|
|
565
790
|
baseUrl: "https://api.x.ai"
|
|
566
791
|
});
|
|
567
792
|
|
|
@@ -973,14 +1198,14 @@ var resolveImageModels = (imageModels) => {
|
|
|
973
1198
|
}
|
|
974
1199
|
return new Set(imageModels);
|
|
975
1200
|
};
|
|
976
|
-
var openaiResponses = (
|
|
977
|
-
const baseUrl =
|
|
978
|
-
const imageModels = resolveImageModels(
|
|
1201
|
+
var openaiResponses = (config2) => {
|
|
1202
|
+
const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL2;
|
|
1203
|
+
const imageModels = resolveImageModels(config2.imageModels);
|
|
979
1204
|
return instrumentAIProvider({
|
|
980
1205
|
stream: (params) => {
|
|
981
1206
|
const isImageModel = imageModels.has(params.model);
|
|
982
1207
|
const body = buildRequestBody2(params, isImageModel);
|
|
983
|
-
return fetchResponsesStream(baseUrl,
|
|
1208
|
+
return fetchResponsesStream(baseUrl, config2.apiKey, body, params.signal);
|
|
984
1209
|
}
|
|
985
1210
|
}, "openai-responses");
|
|
986
1211
|
};
|
|
@@ -1265,14 +1490,14 @@ var resolveImageModels2 = (raw) => {
|
|
|
1265
1490
|
}
|
|
1266
1491
|
return new Set(raw);
|
|
1267
1492
|
};
|
|
1268
|
-
var gemini = (
|
|
1269
|
-
const baseUrl =
|
|
1270
|
-
const imageModels = resolveImageModels2(
|
|
1493
|
+
var gemini = (config2) => {
|
|
1494
|
+
const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL3;
|
|
1495
|
+
const imageModels = resolveImageModels2(config2.imageModels);
|
|
1271
1496
|
return instrumentAIProvider({
|
|
1272
1497
|
stream: (params) => {
|
|
1273
1498
|
const isImageModel = imageModels.has(params.model);
|
|
1274
1499
|
const body = buildRequestBody3(params, isImageModel);
|
|
1275
|
-
return fetchGeminiStream(baseUrl,
|
|
1500
|
+
return fetchGeminiStream(baseUrl, config2.apiKey, params.model, body, params.signal);
|
|
1276
1501
|
}
|
|
1277
1502
|
}, "gemini");
|
|
1278
1503
|
};
|
|
@@ -1545,7 +1770,14 @@ var handleMessageStart = (parsed, state) => {
|
|
|
1545
1770
|
var handleError = (parsed) => {
|
|
1546
1771
|
const error = getRecord(parsed, "error");
|
|
1547
1772
|
const errorMessage = error ? getString(error, "message") : "";
|
|
1548
|
-
|
|
1773
|
+
const errorType = error ? getString(error, "type") : "";
|
|
1774
|
+
const retryable = errorType === "overloaded_error" || errorType === "rate_limit_error" || errorType === "api_error";
|
|
1775
|
+
throw new ProviderError({
|
|
1776
|
+
message: errorMessage || "Anthropic API error",
|
|
1777
|
+
provider: "anthropic",
|
|
1778
|
+
retryable,
|
|
1779
|
+
type: errorType || null
|
|
1780
|
+
});
|
|
1549
1781
|
};
|
|
1550
1782
|
var processEvent = (eventType, parsed, state) => {
|
|
1551
1783
|
switch (eventType) {
|
|
@@ -1654,7 +1886,7 @@ async function* parseSSEStream4(body, signal) {
|
|
|
1654
1886
|
reader.releaseLock();
|
|
1655
1887
|
}
|
|
1656
1888
|
}
|
|
1657
|
-
var fetchAndStream = async function* (baseUrl,
|
|
1889
|
+
var fetchAndStream = async function* (baseUrl, config2, params) {
|
|
1658
1890
|
const body = buildRequestBody4(params);
|
|
1659
1891
|
const target = `${baseUrl}/v1/messages`;
|
|
1660
1892
|
const response = await fetch(target, {
|
|
@@ -1663,24 +1895,28 @@ var fetchAndStream = async function* (baseUrl, config, params) {
|
|
|
1663
1895
|
headers: {
|
|
1664
1896
|
"anthropic-version": API_VERSION,
|
|
1665
1897
|
"Content-Type": "application/json",
|
|
1666
|
-
"x-api-key":
|
|
1898
|
+
"x-api-key": config2.apiKey
|
|
1667
1899
|
},
|
|
1668
1900
|
method: "POST",
|
|
1669
1901
|
signal: params.signal
|
|
1670
1902
|
});
|
|
1671
1903
|
if (!response.ok) {
|
|
1672
1904
|
const errorText = await response.text();
|
|
1673
|
-
throw
|
|
1905
|
+
throw ProviderError.fromResponse("anthropic", response.status, errorText);
|
|
1674
1906
|
}
|
|
1675
1907
|
if (!response.body) {
|
|
1676
|
-
throw new
|
|
1908
|
+
throw new ProviderError({
|
|
1909
|
+
message: "Anthropic API returned no response body",
|
|
1910
|
+
provider: "anthropic",
|
|
1911
|
+
retryable: true
|
|
1912
|
+
});
|
|
1677
1913
|
}
|
|
1678
1914
|
yield* parseSSEStream4(response.body, params.signal);
|
|
1679
1915
|
};
|
|
1680
|
-
var anthropic = (
|
|
1681
|
-
const baseUrl =
|
|
1916
|
+
var anthropic = (config2) => {
|
|
1917
|
+
const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL4;
|
|
1682
1918
|
return instrumentAIProvider({
|
|
1683
|
-
stream: (params) => fetchAndStream(baseUrl,
|
|
1919
|
+
stream: (params) => fetchAndStream(baseUrl, config2, params)
|
|
1684
1920
|
}, "anthropic");
|
|
1685
1921
|
};
|
|
1686
1922
|
|
|
@@ -1915,8 +2151,8 @@ var fetchAndStream2 = async function* (baseUrl, params) {
|
|
|
1915
2151
|
}
|
|
1916
2152
|
yield* parseNDJSONStream(response.body, params.signal);
|
|
1917
2153
|
};
|
|
1918
|
-
var ollama = (
|
|
1919
|
-
const baseUrl =
|
|
2154
|
+
var ollama = (config2 = {}) => {
|
|
2155
|
+
const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL5;
|
|
1920
2156
|
return instrumentAIProvider({
|
|
1921
2157
|
stream: (params) => fetchAndStream2(baseUrl, params)
|
|
1922
2158
|
}, "ollama");
|
|
@@ -2747,24 +2983,24 @@ var buildUserMessage = (content, attachments) => {
|
|
|
2747
2983
|
}
|
|
2748
2984
|
return { content, role: "user" };
|
|
2749
2985
|
};
|
|
2750
|
-
var resolveModel = (
|
|
2986
|
+
var resolveModel = (config2, parsed) => {
|
|
2751
2987
|
if (parsed.model) {
|
|
2752
2988
|
return parsed.model;
|
|
2753
2989
|
}
|
|
2754
|
-
if (typeof
|
|
2755
|
-
return
|
|
2990
|
+
if (typeof config2.model === "string") {
|
|
2991
|
+
return config2.model;
|
|
2756
2992
|
}
|
|
2757
|
-
if (typeof
|
|
2758
|
-
return
|
|
2993
|
+
if (typeof config2.model === "function") {
|
|
2994
|
+
return config2.model(parsed.providerName);
|
|
2759
2995
|
}
|
|
2760
2996
|
return parsed.providerName;
|
|
2761
2997
|
};
|
|
2762
|
-
var resolveTools = (
|
|
2763
|
-
var resolveReasoning = (
|
|
2764
|
-
var aiChat = (
|
|
2765
|
-
const path =
|
|
2766
|
-
const store =
|
|
2767
|
-
const parseProvider =
|
|
2998
|
+
var resolveTools = (config2, providerName, model) => typeof config2.tools === "function" ? config2.tools(providerName, model) : config2.tools;
|
|
2999
|
+
var resolveReasoning = (config2, providerName, model) => typeof config2.reasoning === "function" ? config2.reasoning(providerName, model) : config2.reasoning;
|
|
3000
|
+
var aiChat = (config2) => {
|
|
3001
|
+
const path = config2.path ?? DEFAULT_PATH;
|
|
3002
|
+
const store = config2.store ?? createMemoryStore();
|
|
3003
|
+
const parseProvider = config2.parseProvider ?? defaultParseProvider;
|
|
2768
3004
|
const abortControllers = new Map;
|
|
2769
3005
|
const handleCancel = (conversationId) => {
|
|
2770
3006
|
const controller = abortControllers.get(conversationId);
|
|
@@ -2802,17 +3038,17 @@ var aiChat = (config) => {
|
|
|
2802
3038
|
timestamp: Date.now()
|
|
2803
3039
|
});
|
|
2804
3040
|
await store.set(conversationId, conversation);
|
|
2805
|
-
const model = resolveModel(
|
|
3041
|
+
const model = resolveModel(config2, parsed);
|
|
2806
3042
|
const userMessage = buildUserMessage(content, attachments);
|
|
2807
3043
|
await streamAI(ws, conversationId, messageId, {
|
|
2808
|
-
maxTurns:
|
|
3044
|
+
maxTurns: config2.maxTurns,
|
|
2809
3045
|
messages: [...history, userMessage],
|
|
2810
3046
|
model,
|
|
2811
|
-
provider:
|
|
3047
|
+
provider: config2.provider(providerName),
|
|
2812
3048
|
signal: controller.signal,
|
|
2813
|
-
systemPrompt:
|
|
2814
|
-
reasoning: resolveReasoning(
|
|
2815
|
-
tools: resolveTools(
|
|
3049
|
+
systemPrompt: config2.systemPrompt,
|
|
3050
|
+
reasoning: resolveReasoning(config2, providerName, model),
|
|
3051
|
+
tools: resolveTools(config2, providerName, model),
|
|
2816
3052
|
onComplete: async (fullResponse, usage) => {
|
|
2817
3053
|
const conv = await store.get(conversationId);
|
|
2818
3054
|
if (conv) {
|
|
@@ -2826,15 +3062,15 @@ var aiChat = (config) => {
|
|
|
2826
3062
|
await store.set(conversationId, conv);
|
|
2827
3063
|
}
|
|
2828
3064
|
abortControllers.delete(conversationId);
|
|
2829
|
-
|
|
3065
|
+
config2.onComplete?.(conversationId, fullResponse, usage);
|
|
2830
3066
|
}
|
|
2831
3067
|
});
|
|
2832
3068
|
};
|
|
2833
3069
|
const htmxRoutes = () => {
|
|
2834
|
-
if (!
|
|
3070
|
+
if (!config2.htmx) {
|
|
2835
3071
|
return new Elysia;
|
|
2836
3072
|
}
|
|
2837
|
-
const renderers = resolveRenderers(typeof
|
|
3073
|
+
const renderers = resolveRenderers(typeof config2.htmx === "object" ? config2.htmx.render : undefined);
|
|
2838
3074
|
return new Elysia().post(`${path}/message`, async ({ body }) => {
|
|
2839
3075
|
const requestBody = body && typeof body === "object" ? body : {};
|
|
2840
3076
|
const rawContent = "content" in requestBody ? String(requestBody.content) : undefined;
|
|
@@ -2872,21 +3108,21 @@ var aiChat = (config) => {
|
|
|
2872
3108
|
}
|
|
2873
3109
|
const parsed = parseProvider(conversation.messages.at(EXCLUDE_LAST_OFFSET)?.content ?? "");
|
|
2874
3110
|
const { providerName } = parsed;
|
|
2875
|
-
const model = resolveModel(
|
|
3111
|
+
const model = resolveModel(config2, parsed);
|
|
2876
3112
|
const controller = new AbortController;
|
|
2877
3113
|
abortControllers.set(conversationId, controller);
|
|
2878
3114
|
const history = getHistory(conversation);
|
|
2879
3115
|
const lastMsg = conversation.messages.at(EXCLUDE_LAST_OFFSET);
|
|
2880
3116
|
const userMessage = buildUserMessage(lastMsg?.content ?? "", lastMsg?.attachments);
|
|
2881
3117
|
const sseStream = streamAIToSSE(conversationId, messageId, {
|
|
2882
|
-
maxTurns:
|
|
3118
|
+
maxTurns: config2.maxTurns,
|
|
2883
3119
|
messages: [...history.slice(0, EXCLUDE_LAST_OFFSET), userMessage],
|
|
2884
3120
|
model,
|
|
2885
|
-
provider:
|
|
3121
|
+
provider: config2.provider(providerName),
|
|
2886
3122
|
signal: controller.signal,
|
|
2887
|
-
systemPrompt:
|
|
2888
|
-
reasoning: resolveReasoning(
|
|
2889
|
-
tools: resolveTools(
|
|
3123
|
+
systemPrompt: config2.systemPrompt,
|
|
3124
|
+
reasoning: resolveReasoning(config2, providerName, model),
|
|
3125
|
+
tools: resolveTools(config2, providerName, model)
|
|
2890
3126
|
}, renderers);
|
|
2891
3127
|
for await (const event of sseStream) {
|
|
2892
3128
|
yield event;
|
|
@@ -3742,12 +3978,12 @@ var createAIStream = (path, conversationId) => {
|
|
|
3742
3978
|
let currentMessages = [];
|
|
3743
3979
|
let activeConversationId = conversationId ?? null;
|
|
3744
3980
|
const syncState = () => {
|
|
3745
|
-
const
|
|
3746
|
-
const convId = activeConversationId ??
|
|
3747
|
-
const conversation = convId ?
|
|
3748
|
-
activeConversationId = convId ??
|
|
3749
|
-
currentError =
|
|
3750
|
-
currentIsStreaming =
|
|
3981
|
+
const snapshot2 = store.getSnapshot();
|
|
3982
|
+
const convId = activeConversationId ?? snapshot2.activeConversationId;
|
|
3983
|
+
const conversation = convId ? snapshot2.conversations.get(convId) : undefined;
|
|
3984
|
+
activeConversationId = convId ?? snapshot2.activeConversationId;
|
|
3985
|
+
currentError = snapshot2.error;
|
|
3986
|
+
currentIsStreaming = snapshot2.isStreaming;
|
|
3751
3987
|
currentMessages = conversation?.messages ?? [];
|
|
3752
3988
|
listeners.forEach((listener) => listener());
|
|
3753
3989
|
};
|
|
@@ -3828,35 +4064,35 @@ var readOAuth2ErrorDetail = async (response) => {
|
|
|
3828
4064
|
const text = await response.clone().text().catch(() => "");
|
|
3829
4065
|
return text.trim();
|
|
3830
4066
|
};
|
|
3831
|
-
var createOAuth2ClientCredentialsTokenSource = (
|
|
3832
|
-
const fetchImpl =
|
|
3833
|
-
const now =
|
|
3834
|
-
const skewMs =
|
|
3835
|
-
const fallbackTtl =
|
|
3836
|
-
const scope = Array.isArray(
|
|
4067
|
+
var createOAuth2ClientCredentialsTokenSource = (config2) => {
|
|
4068
|
+
const fetchImpl = config2.fetch ?? fetch;
|
|
4069
|
+
const now = config2.now ?? (() => Date.now());
|
|
4070
|
+
const skewMs = config2.expirySkewMs ?? 60000;
|
|
4071
|
+
const fallbackTtl = config2.fallbackTtlSeconds ?? 3600;
|
|
4072
|
+
const scope = Array.isArray(config2.scope) ? config2.scope.join(" ") : config2.scope;
|
|
3837
4073
|
let cached = null;
|
|
3838
4074
|
let inflight = null;
|
|
3839
4075
|
const requestToken = async () => {
|
|
3840
4076
|
const body = new URLSearchParams({ grant_type: "client_credentials" });
|
|
3841
4077
|
if (scope)
|
|
3842
4078
|
body.set("scope", scope);
|
|
3843
|
-
if (
|
|
3844
|
-
body.set("audience",
|
|
3845
|
-
for (const [key, value] of Object.entries(
|
|
4079
|
+
if (config2.audience)
|
|
4080
|
+
body.set("audience", config2.audience);
|
|
4081
|
+
for (const [key, value] of Object.entries(config2.extraParams ?? {})) {
|
|
3846
4082
|
body.set(key, value);
|
|
3847
4083
|
}
|
|
3848
4084
|
const headers = {
|
|
3849
4085
|
Accept: "application/json",
|
|
3850
4086
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
3851
4087
|
};
|
|
3852
|
-
if (
|
|
3853
|
-
const encoded = btoa(`${
|
|
4088
|
+
if (config2.authStyle === "basic") {
|
|
4089
|
+
const encoded = btoa(`${config2.clientId}:${config2.clientSecret}`);
|
|
3854
4090
|
headers.Authorization = `Basic ${encoded}`;
|
|
3855
4091
|
} else {
|
|
3856
|
-
body.set("client_id",
|
|
3857
|
-
body.set("client_secret",
|
|
4092
|
+
body.set("client_id", config2.clientId);
|
|
4093
|
+
body.set("client_secret", config2.clientSecret);
|
|
3858
4094
|
}
|
|
3859
|
-
const response = await fetchImpl(
|
|
4095
|
+
const response = await fetchImpl(config2.tokenUrl, {
|
|
3860
4096
|
body,
|
|
3861
4097
|
headers,
|
|
3862
4098
|
method: "POST"
|
|
@@ -3890,11 +4126,13 @@ var createOAuth2ClientCredentialsTokenSource = (config) => {
|
|
|
3890
4126
|
};
|
|
3891
4127
|
export {
|
|
3892
4128
|
xai,
|
|
4129
|
+
withResilience,
|
|
3893
4130
|
streamAIToSSE,
|
|
3894
4131
|
streamAI,
|
|
3895
4132
|
serverMessageToAction,
|
|
3896
4133
|
serializeAIMessage,
|
|
3897
4134
|
resolveRenderers,
|
|
4135
|
+
providerStatusPage,
|
|
3898
4136
|
parseAIMessage,
|
|
3899
4137
|
openaiResponses,
|
|
3900
4138
|
openaiCompatible,
|
|
@@ -3904,6 +4142,7 @@ export {
|
|
|
3904
4142
|
mistralai,
|
|
3905
4143
|
meta,
|
|
3906
4144
|
google,
|
|
4145
|
+
getProviderHealth,
|
|
3907
4146
|
generateObjectAI,
|
|
3908
4147
|
generateId,
|
|
3909
4148
|
generateAIWithTools,
|
|
@@ -3916,10 +4155,13 @@ export {
|
|
|
3916
4155
|
createConversationManager,
|
|
3917
4156
|
createAIStream,
|
|
3918
4157
|
createAIConnection,
|
|
4158
|
+
configureProviderResilience,
|
|
3919
4159
|
anthropic,
|
|
3920
4160
|
alibaba,
|
|
3921
|
-
aiChat
|
|
4161
|
+
aiChat,
|
|
4162
|
+
ProviderError,
|
|
4163
|
+
PROVIDER_STATUS_PAGES
|
|
3922
4164
|
};
|
|
3923
4165
|
|
|
3924
|
-
//# debugId=
|
|
4166
|
+
//# debugId=4F9632D62D06633E64756E2164756E21
|
|
3925
4167
|
//# sourceMappingURL=index.js.map
|