@absolutejs/ai 0.0.21 → 0.0.23

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
@@ -1,15 +1,256 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
- // src/ai/providers/instrumentation.ts
5
- var instrumentAIProvider = (provider, providerName) => ({
6
- stream: (params) => {
7
- if (!params.onUsage && !params.onSpan) {
8
- return provider.stream(params);
9
- }
10
- return tapStream(provider.stream(params), params, providerName);
4
+ // src/ai/errors/providerError.ts
5
+ var PROVIDER_STATUS_PAGES = {
6
+ anthropic: "https://status.claude.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
+ });
11
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
+ });
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
+ external: null,
105
+ lastError: null,
106
+ lastFailureAt: null,
107
+ lastSuccessAt: null,
108
+ nextProbeAt: null,
109
+ state: "closed"
110
+ };
111
+ health.set(provider, fresh);
112
+ return fresh;
113
+ };
114
+ var setProviderAvailability = (provider, status) => {
115
+ const record = recordFor(provider);
116
+ record.external = {
117
+ available: status.available,
118
+ checkedAt: Date.now(),
119
+ indicator: status.indicator ?? (status.available ? "operational" : "unknown"),
120
+ reason: status.reason ?? ""
121
+ };
122
+ };
123
+ var noteSuccess = (provider) => {
124
+ const record = recordFor(provider);
125
+ record.state = "closed";
126
+ record.consecutiveFailures = 0;
127
+ record.nextProbeAt = null;
128
+ record.lastError = null;
129
+ record.lastSuccessAt = Date.now();
130
+ };
131
+ var noteFailure = (provider, error) => {
132
+ const record = recordFor(provider);
133
+ record.consecutiveFailures += 1;
134
+ record.lastFailureAt = Date.now();
135
+ record.lastError = {
136
+ message: error.message,
137
+ status: error.status,
138
+ type: error.type
139
+ };
140
+ if (record.consecutiveFailures >= config.failureThreshold) {
141
+ record.state = "open";
142
+ record.nextProbeAt = Date.now() + config.openMs;
143
+ }
144
+ };
145
+ var snapshot = (provider, record) => ({
146
+ consecutiveFailures: record.consecutiveFailures,
147
+ external: record.external,
148
+ healthy: record.state === "closed" && record.external?.available !== false,
149
+ lastError: record.lastError,
150
+ lastFailureAt: record.lastFailureAt,
151
+ lastSuccessAt: record.lastSuccessAt,
152
+ nextRetryAt: record.nextProbeAt,
153
+ provider,
154
+ state: record.state,
155
+ statusPageUrl: providerStatusPage(provider)
12
156
  });
157
+ function getProviderHealth(provider) {
158
+ if (provider !== undefined)
159
+ return snapshot(provider, recordFor(provider));
160
+ return [...health.entries()].map(([name, record]) => snapshot(name, record));
161
+ }
162
+ var backoffDelay = (attempt) => {
163
+ const exponential = config.baseDelayMs * 2 ** attempt;
164
+ const capped = Math.min(exponential, config.maxDelayMs);
165
+ return Math.round(capped / 2 + Math.random() * capped / 2);
166
+ };
167
+ var sleep = (ms, signal) => new Promise((resolve, reject) => {
168
+ if (signal?.aborted) {
169
+ reject(signal.reason ?? new Error("aborted"));
170
+ return;
171
+ }
172
+ const timer = setTimeout(() => {
173
+ signal?.removeEventListener("abort", onAbort);
174
+ resolve();
175
+ }, ms);
176
+ const onAbort = () => {
177
+ clearTimeout(timer);
178
+ reject(signal?.reason ?? new Error("aborted"));
179
+ };
180
+ signal?.addEventListener("abort", onAbort, { once: true });
181
+ });
182
+ var circuitOpen = (provider) => {
183
+ const record = recordFor(provider);
184
+ if (record.external && !record.external.available) {
185
+ return new ProviderError({
186
+ message: `${provider} API is reported down${record.external.reason ? `: ${record.external.reason}` : ""}`,
187
+ provider,
188
+ retryable: true,
189
+ status: null,
190
+ type: record.external.indicator
191
+ });
192
+ }
193
+ if (record.state !== "open")
194
+ return null;
195
+ if (record.nextProbeAt !== null && Date.now() >= record.nextProbeAt) {
196
+ record.state = "half-open";
197
+ return null;
198
+ }
199
+ const detail = record.lastError?.message ?? "recent failures";
200
+ return new ProviderError({
201
+ message: `${provider} is temporarily unavailable (circuit open after ${record.consecutiveFailures} failures): ${detail}`,
202
+ provider,
203
+ retryable: true,
204
+ status: record.lastError?.status ?? null,
205
+ type: record.lastError?.type ?? null
206
+ });
207
+ };
208
+ var withResilience = (provider, providerName = "unknown") => {
209
+ const attempt = async function* (params, attemptNo) {
210
+ let yielded = false;
211
+ try {
212
+ for await (const chunk of provider.stream(params)) {
213
+ yielded = true;
214
+ yield chunk;
215
+ }
216
+ noteSuccess(providerName);
217
+ } catch (err) {
218
+ const providerError = ProviderError.from(err, providerName);
219
+ const canRetry = !yielded && providerError.retryable && attemptNo < config.maxRetries && !params.signal?.aborted;
220
+ if (canRetry) {
221
+ await sleep(backoffDelay(attemptNo), params.signal);
222
+ yield* attempt(params, attemptNo + 1);
223
+ return;
224
+ }
225
+ noteFailure(providerName, providerError);
226
+ throw providerError;
227
+ }
228
+ };
229
+ return {
230
+ stream: (params) => {
231
+ const tripped = circuitOpen(providerName);
232
+ if (tripped) {
233
+ return async function* () {
234
+ throw tripped;
235
+ }();
236
+ }
237
+ return attempt(params, 0);
238
+ }
239
+ };
240
+ };
241
+
242
+ // src/ai/providers/instrumentation.ts
243
+ var instrumentAIProvider = (provider, providerName) => {
244
+ const resilient = withResilience(provider, providerName);
245
+ return {
246
+ stream: (params) => {
247
+ if (!params.onUsage && !params.onSpan) {
248
+ return resilient.stream(params);
249
+ }
250
+ return tapStream(resilient.stream(params), params, providerName);
251
+ }
252
+ };
253
+ };
13
254
  async function* tapStream(source, params, providerName) {
14
255
  const startedAt = Date.now();
15
256
  let lastUsage;
@@ -501,23 +742,27 @@ var fetchOpenAIStream = async function* (baseUrl, apiKey, body, signal) {
501
742
  });
502
743
  if (!response.ok) {
503
744
  const errorText = await response.text();
504
- throw new Error(`OpenAI API error ${response.status}: ${errorText}`);
745
+ throw ProviderError.fromResponse("openai", response.status, errorText);
505
746
  }
506
747
  if (!response.body) {
507
- throw new Error("OpenAI API returned no response body");
748
+ throw new ProviderError({
749
+ message: "OpenAI API returned no response body",
750
+ provider: "openai",
751
+ retryable: true
752
+ });
508
753
  }
509
754
  yield* parseSSEStream(response.body, signal);
510
755
  };
511
- var openai = (config) => {
512
- const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
513
- if (!config.apiKey && !config.tokenSource) {
756
+ var openai = (config2) => {
757
+ const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL;
758
+ if (!config2.apiKey && !config2.tokenSource) {
514
759
  throw new Error("openai() requires either apiKey or tokenSource");
515
760
  }
516
761
  const resolveKey = async () => {
517
- if (config.tokenSource) {
518
- return await Promise.resolve(config.tokenSource());
762
+ if (config2.tokenSource) {
763
+ return await Promise.resolve(config2.tokenSource());
519
764
  }
520
- return config.apiKey;
765
+ return config2.apiKey;
521
766
  };
522
767
  return instrumentAIProvider({
523
768
  stream: (params) => {
@@ -531,37 +776,37 @@ var openai = (config) => {
531
776
  };
532
777
 
533
778
  // src/ai/providers/openaiCompatible.ts
534
- var alibaba = (config) => openaiCompatible({
535
- apiKey: config.apiKey,
779
+ var alibaba = (config2) => openaiCompatible({
780
+ apiKey: config2.apiKey,
536
781
  baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode"
537
782
  });
538
- var deepseek = (config) => openaiCompatible({
539
- apiKey: config.apiKey,
783
+ var deepseek = (config2) => openaiCompatible({
784
+ apiKey: config2.apiKey,
540
785
  baseUrl: "https://api.deepseek.com"
541
786
  });
542
- var google = (config) => openaiCompatible({
543
- apiKey: config.apiKey,
787
+ var google = (config2) => openaiCompatible({
788
+ apiKey: config2.apiKey,
544
789
  baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai"
545
790
  });
546
- var meta = (config) => openaiCompatible({
547
- apiKey: config.apiKey,
791
+ var meta = (config2) => openaiCompatible({
792
+ apiKey: config2.apiKey,
548
793
  baseUrl: "https://api.llama.com/compat/v1"
549
794
  });
550
- var mistralai = (config) => openaiCompatible({
551
- apiKey: config.apiKey,
795
+ var mistralai = (config2) => openaiCompatible({
796
+ apiKey: config2.apiKey,
552
797
  baseUrl: "https://api.mistral.ai"
553
798
  });
554
- var moonshot = (config) => openaiCompatible({
555
- apiKey: config.apiKey,
799
+ var moonshot = (config2) => openaiCompatible({
800
+ apiKey: config2.apiKey,
556
801
  baseUrl: "https://api.moonshot.ai"
557
802
  });
558
- var openaiCompatible = (config) => openai({
559
- apiKey: config.apiKey,
560
- baseUrl: config.baseUrl,
561
- tokenSource: config.tokenSource
803
+ var openaiCompatible = (config2) => openai({
804
+ apiKey: config2.apiKey,
805
+ baseUrl: config2.baseUrl,
806
+ tokenSource: config2.tokenSource
562
807
  });
563
- var xai = (config) => openaiCompatible({
564
- apiKey: config.apiKey,
808
+ var xai = (config2) => openaiCompatible({
809
+ apiKey: config2.apiKey,
565
810
  baseUrl: "https://api.x.ai"
566
811
  });
567
812
 
@@ -973,14 +1218,14 @@ var resolveImageModels = (imageModels) => {
973
1218
  }
974
1219
  return new Set(imageModels);
975
1220
  };
976
- var openaiResponses = (config) => {
977
- const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL2;
978
- const imageModels = resolveImageModels(config.imageModels);
1221
+ var openaiResponses = (config2) => {
1222
+ const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL2;
1223
+ const imageModels = resolveImageModels(config2.imageModels);
979
1224
  return instrumentAIProvider({
980
1225
  stream: (params) => {
981
1226
  const isImageModel = imageModels.has(params.model);
982
1227
  const body = buildRequestBody2(params, isImageModel);
983
- return fetchResponsesStream(baseUrl, config.apiKey, body, params.signal);
1228
+ return fetchResponsesStream(baseUrl, config2.apiKey, body, params.signal);
984
1229
  }
985
1230
  }, "openai-responses");
986
1231
  };
@@ -1265,14 +1510,14 @@ var resolveImageModels2 = (raw) => {
1265
1510
  }
1266
1511
  return new Set(raw);
1267
1512
  };
1268
- var gemini = (config) => {
1269
- const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL3;
1270
- const imageModels = resolveImageModels2(config.imageModels);
1513
+ var gemini = (config2) => {
1514
+ const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL3;
1515
+ const imageModels = resolveImageModels2(config2.imageModels);
1271
1516
  return instrumentAIProvider({
1272
1517
  stream: (params) => {
1273
1518
  const isImageModel = imageModels.has(params.model);
1274
1519
  const body = buildRequestBody3(params, isImageModel);
1275
- return fetchGeminiStream(baseUrl, config.apiKey, params.model, body, params.signal);
1520
+ return fetchGeminiStream(baseUrl, config2.apiKey, params.model, body, params.signal);
1276
1521
  }
1277
1522
  }, "gemini");
1278
1523
  };
@@ -1545,7 +1790,14 @@ var handleMessageStart = (parsed, state) => {
1545
1790
  var handleError = (parsed) => {
1546
1791
  const error = getRecord(parsed, "error");
1547
1792
  const errorMessage = error ? getString(error, "message") : "";
1548
- throw new Error(errorMessage || "Anthropic API error");
1793
+ const errorType = error ? getString(error, "type") : "";
1794
+ const retryable = errorType === "overloaded_error" || errorType === "rate_limit_error" || errorType === "api_error";
1795
+ throw new ProviderError({
1796
+ message: errorMessage || "Anthropic API error",
1797
+ provider: "anthropic",
1798
+ retryable,
1799
+ type: errorType || null
1800
+ });
1549
1801
  };
1550
1802
  var processEvent = (eventType, parsed, state) => {
1551
1803
  switch (eventType) {
@@ -1654,7 +1906,7 @@ async function* parseSSEStream4(body, signal) {
1654
1906
  reader.releaseLock();
1655
1907
  }
1656
1908
  }
1657
- var fetchAndStream = async function* (baseUrl, config, params) {
1909
+ var fetchAndStream = async function* (baseUrl, config2, params) {
1658
1910
  const body = buildRequestBody4(params);
1659
1911
  const target = `${baseUrl}/v1/messages`;
1660
1912
  const response = await fetch(target, {
@@ -1663,24 +1915,28 @@ var fetchAndStream = async function* (baseUrl, config, params) {
1663
1915
  headers: {
1664
1916
  "anthropic-version": API_VERSION,
1665
1917
  "Content-Type": "application/json",
1666
- "x-api-key": config.apiKey
1918
+ "x-api-key": config2.apiKey
1667
1919
  },
1668
1920
  method: "POST",
1669
1921
  signal: params.signal
1670
1922
  });
1671
1923
  if (!response.ok) {
1672
1924
  const errorText = await response.text();
1673
- throw new Error(`Anthropic API error ${response.status}: ${errorText}`);
1925
+ throw ProviderError.fromResponse("anthropic", response.status, errorText);
1674
1926
  }
1675
1927
  if (!response.body) {
1676
- throw new Error("Anthropic API returned no response body");
1928
+ throw new ProviderError({
1929
+ message: "Anthropic API returned no response body",
1930
+ provider: "anthropic",
1931
+ retryable: true
1932
+ });
1677
1933
  }
1678
1934
  yield* parseSSEStream4(response.body, params.signal);
1679
1935
  };
1680
- var anthropic = (config) => {
1681
- const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL4;
1936
+ var anthropic = (config2) => {
1937
+ const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL4;
1682
1938
  return instrumentAIProvider({
1683
- stream: (params) => fetchAndStream(baseUrl, config, params)
1939
+ stream: (params) => fetchAndStream(baseUrl, config2, params)
1684
1940
  }, "anthropic");
1685
1941
  };
1686
1942
 
@@ -1915,8 +2171,8 @@ var fetchAndStream2 = async function* (baseUrl, params) {
1915
2171
  }
1916
2172
  yield* parseNDJSONStream(response.body, params.signal);
1917
2173
  };
1918
- var ollama = (config = {}) => {
1919
- const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL5;
2174
+ var ollama = (config2 = {}) => {
2175
+ const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL5;
1920
2176
  return instrumentAIProvider({
1921
2177
  stream: (params) => fetchAndStream2(baseUrl, params)
1922
2178
  }, "ollama");
@@ -2747,24 +3003,24 @@ var buildUserMessage = (content, attachments) => {
2747
3003
  }
2748
3004
  return { content, role: "user" };
2749
3005
  };
2750
- var resolveModel = (config, parsed) => {
3006
+ var resolveModel = (config2, parsed) => {
2751
3007
  if (parsed.model) {
2752
3008
  return parsed.model;
2753
3009
  }
2754
- if (typeof config.model === "string") {
2755
- return config.model;
3010
+ if (typeof config2.model === "string") {
3011
+ return config2.model;
2756
3012
  }
2757
- if (typeof config.model === "function") {
2758
- return config.model(parsed.providerName);
3013
+ if (typeof config2.model === "function") {
3014
+ return config2.model(parsed.providerName);
2759
3015
  }
2760
3016
  return parsed.providerName;
2761
3017
  };
2762
- var resolveTools = (config, providerName, model) => typeof config.tools === "function" ? config.tools(providerName, model) : config.tools;
2763
- var resolveReasoning = (config, providerName, model) => typeof config.reasoning === "function" ? config.reasoning(providerName, model) : config.reasoning;
2764
- var aiChat = (config) => {
2765
- const path = config.path ?? DEFAULT_PATH;
2766
- const store = config.store ?? createMemoryStore();
2767
- const parseProvider = config.parseProvider ?? defaultParseProvider;
3018
+ var resolveTools = (config2, providerName, model) => typeof config2.tools === "function" ? config2.tools(providerName, model) : config2.tools;
3019
+ var resolveReasoning = (config2, providerName, model) => typeof config2.reasoning === "function" ? config2.reasoning(providerName, model) : config2.reasoning;
3020
+ var aiChat = (config2) => {
3021
+ const path = config2.path ?? DEFAULT_PATH;
3022
+ const store = config2.store ?? createMemoryStore();
3023
+ const parseProvider = config2.parseProvider ?? defaultParseProvider;
2768
3024
  const abortControllers = new Map;
2769
3025
  const handleCancel = (conversationId) => {
2770
3026
  const controller = abortControllers.get(conversationId);
@@ -2802,17 +3058,17 @@ var aiChat = (config) => {
2802
3058
  timestamp: Date.now()
2803
3059
  });
2804
3060
  await store.set(conversationId, conversation);
2805
- const model = resolveModel(config, parsed);
3061
+ const model = resolveModel(config2, parsed);
2806
3062
  const userMessage = buildUserMessage(content, attachments);
2807
3063
  await streamAI(ws, conversationId, messageId, {
2808
- maxTurns: config.maxTurns,
3064
+ maxTurns: config2.maxTurns,
2809
3065
  messages: [...history, userMessage],
2810
3066
  model,
2811
- provider: config.provider(providerName),
3067
+ provider: config2.provider(providerName),
2812
3068
  signal: controller.signal,
2813
- systemPrompt: config.systemPrompt,
2814
- reasoning: resolveReasoning(config, providerName, model),
2815
- tools: resolveTools(config, providerName, model),
3069
+ systemPrompt: config2.systemPrompt,
3070
+ reasoning: resolveReasoning(config2, providerName, model),
3071
+ tools: resolveTools(config2, providerName, model),
2816
3072
  onComplete: async (fullResponse, usage) => {
2817
3073
  const conv = await store.get(conversationId);
2818
3074
  if (conv) {
@@ -2826,15 +3082,15 @@ var aiChat = (config) => {
2826
3082
  await store.set(conversationId, conv);
2827
3083
  }
2828
3084
  abortControllers.delete(conversationId);
2829
- config.onComplete?.(conversationId, fullResponse, usage);
3085
+ config2.onComplete?.(conversationId, fullResponse, usage);
2830
3086
  }
2831
3087
  });
2832
3088
  };
2833
3089
  const htmxRoutes = () => {
2834
- if (!config.htmx) {
3090
+ if (!config2.htmx) {
2835
3091
  return new Elysia;
2836
3092
  }
2837
- const renderers = resolveRenderers(typeof config.htmx === "object" ? config.htmx.render : undefined);
3093
+ const renderers = resolveRenderers(typeof config2.htmx === "object" ? config2.htmx.render : undefined);
2838
3094
  return new Elysia().post(`${path}/message`, async ({ body }) => {
2839
3095
  const requestBody = body && typeof body === "object" ? body : {};
2840
3096
  const rawContent = "content" in requestBody ? String(requestBody.content) : undefined;
@@ -2872,21 +3128,21 @@ var aiChat = (config) => {
2872
3128
  }
2873
3129
  const parsed = parseProvider(conversation.messages.at(EXCLUDE_LAST_OFFSET)?.content ?? "");
2874
3130
  const { providerName } = parsed;
2875
- const model = resolveModel(config, parsed);
3131
+ const model = resolveModel(config2, parsed);
2876
3132
  const controller = new AbortController;
2877
3133
  abortControllers.set(conversationId, controller);
2878
3134
  const history = getHistory(conversation);
2879
3135
  const lastMsg = conversation.messages.at(EXCLUDE_LAST_OFFSET);
2880
3136
  const userMessage = buildUserMessage(lastMsg?.content ?? "", lastMsg?.attachments);
2881
3137
  const sseStream = streamAIToSSE(conversationId, messageId, {
2882
- maxTurns: config.maxTurns,
3138
+ maxTurns: config2.maxTurns,
2883
3139
  messages: [...history.slice(0, EXCLUDE_LAST_OFFSET), userMessage],
2884
3140
  model,
2885
- provider: config.provider(providerName),
3141
+ provider: config2.provider(providerName),
2886
3142
  signal: controller.signal,
2887
- systemPrompt: config.systemPrompt,
2888
- reasoning: resolveReasoning(config, providerName, model),
2889
- tools: resolveTools(config, providerName, model)
3143
+ systemPrompt: config2.systemPrompt,
3144
+ reasoning: resolveReasoning(config2, providerName, model),
3145
+ tools: resolveTools(config2, providerName, model)
2890
3146
  }, renderers);
2891
3147
  for await (const event of sseStream) {
2892
3148
  yield event;
@@ -3742,12 +3998,12 @@ var createAIStream = (path, conversationId) => {
3742
3998
  let currentMessages = [];
3743
3999
  let activeConversationId = conversationId ?? null;
3744
4000
  const syncState = () => {
3745
- const snapshot = store.getSnapshot();
3746
- const convId = activeConversationId ?? snapshot.activeConversationId;
3747
- const conversation = convId ? snapshot.conversations.get(convId) : undefined;
3748
- activeConversationId = convId ?? snapshot.activeConversationId;
3749
- currentError = snapshot.error;
3750
- currentIsStreaming = snapshot.isStreaming;
4001
+ const snapshot2 = store.getSnapshot();
4002
+ const convId = activeConversationId ?? snapshot2.activeConversationId;
4003
+ const conversation = convId ? snapshot2.conversations.get(convId) : undefined;
4004
+ activeConversationId = convId ?? snapshot2.activeConversationId;
4005
+ currentError = snapshot2.error;
4006
+ currentIsStreaming = snapshot2.isStreaming;
3751
4007
  currentMessages = conversation?.messages ?? [];
3752
4008
  listeners.forEach((listener) => listener());
3753
4009
  };
@@ -3828,35 +4084,35 @@ var readOAuth2ErrorDetail = async (response) => {
3828
4084
  const text = await response.clone().text().catch(() => "");
3829
4085
  return text.trim();
3830
4086
  };
3831
- var createOAuth2ClientCredentialsTokenSource = (config) => {
3832
- const fetchImpl = config.fetch ?? fetch;
3833
- const now = config.now ?? (() => Date.now());
3834
- const skewMs = config.expirySkewMs ?? 60000;
3835
- const fallbackTtl = config.fallbackTtlSeconds ?? 3600;
3836
- const scope = Array.isArray(config.scope) ? config.scope.join(" ") : config.scope;
4087
+ var createOAuth2ClientCredentialsTokenSource = (config2) => {
4088
+ const fetchImpl = config2.fetch ?? fetch;
4089
+ const now = config2.now ?? (() => Date.now());
4090
+ const skewMs = config2.expirySkewMs ?? 60000;
4091
+ const fallbackTtl = config2.fallbackTtlSeconds ?? 3600;
4092
+ const scope = Array.isArray(config2.scope) ? config2.scope.join(" ") : config2.scope;
3837
4093
  let cached = null;
3838
4094
  let inflight = null;
3839
4095
  const requestToken = async () => {
3840
4096
  const body = new URLSearchParams({ grant_type: "client_credentials" });
3841
4097
  if (scope)
3842
4098
  body.set("scope", scope);
3843
- if (config.audience)
3844
- body.set("audience", config.audience);
3845
- for (const [key, value] of Object.entries(config.extraParams ?? {})) {
4099
+ if (config2.audience)
4100
+ body.set("audience", config2.audience);
4101
+ for (const [key, value] of Object.entries(config2.extraParams ?? {})) {
3846
4102
  body.set(key, value);
3847
4103
  }
3848
4104
  const headers = {
3849
4105
  Accept: "application/json",
3850
4106
  "Content-Type": "application/x-www-form-urlencoded"
3851
4107
  };
3852
- if (config.authStyle === "basic") {
3853
- const encoded = btoa(`${config.clientId}:${config.clientSecret}`);
4108
+ if (config2.authStyle === "basic") {
4109
+ const encoded = btoa(`${config2.clientId}:${config2.clientSecret}`);
3854
4110
  headers.Authorization = `Basic ${encoded}`;
3855
4111
  } else {
3856
- body.set("client_id", config.clientId);
3857
- body.set("client_secret", config.clientSecret);
4112
+ body.set("client_id", config2.clientId);
4113
+ body.set("client_secret", config2.clientSecret);
3858
4114
  }
3859
- const response = await fetchImpl(config.tokenUrl, {
4115
+ const response = await fetchImpl(config2.tokenUrl, {
3860
4116
  body,
3861
4117
  headers,
3862
4118
  method: "POST"
@@ -3888,13 +4144,114 @@ var createOAuth2ClientCredentialsTokenSource = (config) => {
3888
4144
  return inflight;
3889
4145
  };
3890
4146
  };
4147
+ // src/ai/providerStatusMonitor.ts
4148
+ var API_COMPONENT_BY_PROVIDER = {
4149
+ anthropic: "Claude API (api.anthropic.com)",
4150
+ openai: "Chat Completions"
4151
+ };
4152
+ var AVAILABLE_COMPONENT_STATUSES = new Set([
4153
+ "operational",
4154
+ "degraded_performance"
4155
+ ]);
4156
+ var AVAILABLE_PAGE_INDICATORS = new Set(["none", "minor"]);
4157
+ var isRecord6 = (value) => typeof value === "object" && value !== null;
4158
+ var readString = (record, key) => typeof record[key] === "string" ? record[key] : "";
4159
+ var findComponentStatus = (body, componentName) => {
4160
+ if (!isRecord6(body) || !Array.isArray(body.components))
4161
+ return null;
4162
+ const match = body.components.filter(isRecord6).find((component) => readString(component, "name") === componentName);
4163
+ return match ? readString(match, "status") : null;
4164
+ };
4165
+ var readPageIndicator = (body) => {
4166
+ if (!isRecord6(body) || !isRecord6(body.status))
4167
+ return null;
4168
+ const indicator = readString(body.status, "indicator");
4169
+ return indicator === "" ? null : indicator;
4170
+ };
4171
+ var fetchJson = async (url, signal) => {
4172
+ const response = await fetch(url, { signal });
4173
+ if (!response.ok)
4174
+ throw new Error(`status fetch ${response.status}`);
4175
+ return response.json();
4176
+ };
4177
+ var unknownStatus = (provider) => ({
4178
+ available: true,
4179
+ checkedAt: Date.now(),
4180
+ componentName: null,
4181
+ indicator: "unknown",
4182
+ provider,
4183
+ reason: "",
4184
+ statusPageUrl: providerStatusPage(provider)
4185
+ });
4186
+ var fetchProviderApiStatus = async (provider, signal) => {
4187
+ const base = PROVIDER_STATUS_PAGES[provider];
4188
+ if (base === undefined)
4189
+ return unknownStatus(provider);
4190
+ try {
4191
+ const componentName = API_COMPONENT_BY_PROVIDER[provider] ?? null;
4192
+ if (componentName !== null) {
4193
+ const components = await fetchJson(`${base}/api/v2/components.json`, signal);
4194
+ const status = findComponentStatus(components, componentName);
4195
+ if (status !== null) {
4196
+ const available2 = AVAILABLE_COMPONENT_STATUSES.has(status);
4197
+ return {
4198
+ available: available2,
4199
+ checkedAt: Date.now(),
4200
+ componentName,
4201
+ indicator: status,
4202
+ provider,
4203
+ reason: available2 ? "" : `${status} on ${componentName}`,
4204
+ statusPageUrl: base
4205
+ };
4206
+ }
4207
+ }
4208
+ const summary = await fetchJson(`${base}/api/v2/status.json`, signal);
4209
+ const indicator = readPageIndicator(summary);
4210
+ if (indicator === null)
4211
+ return unknownStatus(provider);
4212
+ const available = AVAILABLE_PAGE_INDICATORS.has(indicator);
4213
+ return {
4214
+ available,
4215
+ checkedAt: Date.now(),
4216
+ componentName: null,
4217
+ indicator,
4218
+ provider,
4219
+ reason: available ? "" : `page status: ${indicator}`,
4220
+ statusPageUrl: base
4221
+ };
4222
+ } catch {
4223
+ return unknownStatus(provider);
4224
+ }
4225
+ };
4226
+ var startProviderStatusMonitor = (options) => {
4227
+ const providers = options?.providers ?? ["anthropic", "openai"];
4228
+ const intervalMs = options?.intervalMs ?? 60000;
4229
+ const tick = async () => {
4230
+ const results = await Promise.all(providers.map((provider) => fetchProviderApiStatus(provider)));
4231
+ for (const status of results) {
4232
+ setProviderAvailability(status.provider, {
4233
+ available: status.available,
4234
+ indicator: status.indicator,
4235
+ reason: status.reason
4236
+ });
4237
+ options?.onUpdate?.(status);
4238
+ }
4239
+ };
4240
+ tick();
4241
+ const timer = setInterval(() => void tick(), intervalMs);
4242
+ return () => clearInterval(timer);
4243
+ };
3891
4244
  export {
3892
4245
  xai,
4246
+ withResilience,
3893
4247
  streamAIToSSE,
3894
4248
  streamAI,
4249
+ startProviderStatusMonitor,
4250
+ setProviderAvailability,
3895
4251
  serverMessageToAction,
3896
4252
  serializeAIMessage,
3897
4253
  resolveRenderers,
4254
+ providerStatusPage,
3898
4255
  parseAIMessage,
3899
4256
  openaiResponses,
3900
4257
  openaiCompatible,
@@ -3904,11 +4261,13 @@ export {
3904
4261
  mistralai,
3905
4262
  meta,
3906
4263
  google,
4264
+ getProviderHealth,
3907
4265
  generateObjectAI,
3908
4266
  generateId,
3909
4267
  generateAIWithTools,
3910
4268
  generateAI,
3911
4269
  gemini,
4270
+ fetchProviderApiStatus,
3912
4271
  deepseek,
3913
4272
  createSyncConversationStore,
3914
4273
  createOAuth2ClientCredentialsTokenSource,
@@ -3916,10 +4275,13 @@ export {
3916
4275
  createConversationManager,
3917
4276
  createAIStream,
3918
4277
  createAIConnection,
4278
+ configureProviderResilience,
3919
4279
  anthropic,
3920
4280
  alibaba,
3921
- aiChat
4281
+ aiChat,
4282
+ ProviderError,
4283
+ PROVIDER_STATUS_PAGES
3922
4284
  };
3923
4285
 
3924
- //# debugId=8280CFC77A5D2DF464756E2164756E21
4286
+ //# debugId=E077A15D83F1F94464756E2164756E21
3925
4287
  //# sourceMappingURL=index.js.map