@umituz/web-ai 1.0.0 → 1.0.1

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.
Files changed (53) hide show
  1. package/dist/{ABTesting-DDqNhHtP.d.ts → AIErrors-B2ogd1PB.d.ts} +14 -1
  2. package/dist/{ABTesting-DWBtKTMQ.d.cts → AIErrors-C5EasV_c.d.cts} +14 -1
  3. package/dist/{ProviderConfig-a55tLhWT.d.cts → ProviderConfig-DIFa9nmG.d.cts} +10 -1
  4. package/dist/{ProviderConfig-a55tLhWT.d.ts → ProviderConfig-DIFa9nmG.d.ts} +10 -1
  5. package/dist/chat/index.cjs +114 -23
  6. package/dist/chat/index.cjs.map +1 -1
  7. package/dist/chat/index.d.cts +4 -4
  8. package/dist/chat/index.d.ts +4 -4
  9. package/dist/chat/index.js +114 -23
  10. package/dist/chat/index.js.map +1 -1
  11. package/dist/{chat.service-DBKq9I2c.d.cts → chat.service-CL1qtYxe.d.cts} +7 -7
  12. package/dist/{chat.service-RQn8UTXD.d.ts → chat.service-q05GnhEm.d.ts} +7 -7
  13. package/dist/content/index.cjs +393 -186
  14. package/dist/content/index.cjs.map +1 -1
  15. package/dist/content/index.d.cts +7 -8
  16. package/dist/content/index.d.ts +7 -8
  17. package/dist/content/index.js +393 -186
  18. package/dist/content/index.js.map +1 -1
  19. package/dist/core/index.cjs +112 -15
  20. package/dist/core/index.cjs.map +1 -1
  21. package/dist/core/index.d.cts +5 -33
  22. package/dist/core/index.d.ts +5 -33
  23. package/dist/core/index.js +111 -16
  24. package/dist/core/index.js.map +1 -1
  25. package/dist/{create-ai-DGZFBOA1.d.cts → create-ai-DOI9jD3v.d.cts} +4 -21
  26. package/dist/{create-ai-BhNaz86i.d.ts → create-ai-DXEoke8-.d.ts} +4 -21
  27. package/dist/hooks/index.cjs +359 -169
  28. package/dist/hooks/index.cjs.map +1 -1
  29. package/dist/hooks/index.d.cts +6 -6
  30. package/dist/hooks/index.d.ts +6 -6
  31. package/dist/hooks/index.js +359 -169
  32. package/dist/hooks/index.js.map +1 -1
  33. package/dist/index-DIHz8_OJ.d.ts +48 -0
  34. package/dist/index-DKHNcMbl.d.cts +48 -0
  35. package/dist/index.cjs +245 -94
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +6 -6
  38. package/dist/index.d.ts +6 -6
  39. package/dist/index.js +244 -95
  40. package/dist/index.js.map +1 -1
  41. package/dist/{provider.factory-COsnprLH.d.cts → provider.factory-BxsfK4WW.d.cts} +1 -1
  42. package/dist/{provider.factory-CpWFyjbw.d.ts → provider.factory-DfDfk0dK.d.ts} +1 -1
  43. package/dist/providers/index.cjs +281 -124
  44. package/dist/providers/index.cjs.map +1 -1
  45. package/dist/providers/index.d.cts +11 -12
  46. package/dist/providers/index.d.ts +11 -12
  47. package/dist/providers/index.js +281 -124
  48. package/dist/providers/index.js.map +1 -1
  49. package/dist/wizard/index.d.cts +1 -1
  50. package/dist/wizard/index.d.ts +1 -1
  51. package/package.json +4 -1
  52. package/dist/index-DjX9ZKuM.d.cts +0 -14
  53. package/dist/index-DxLvE588.d.ts +0 -14
@@ -0,0 +1,48 @@
1
+ import { c as TextTier } from './index-fXjxTR5W.js';
2
+
3
+ declare enum AIErrorType {
4
+ INVALID_API_KEY = "INVALID_API_KEY",
5
+ MISSING_CONFIG = "MISSING_CONFIG",
6
+ NETWORK_ERROR = "NETWORK_ERROR",
7
+ ABORT_ERROR = "ABORT_ERROR",
8
+ RATE_LIMIT_ERROR = "RATE_LIMIT_ERROR",
9
+ SERVER_ERROR = "SERVER_ERROR",
10
+ CLIENT_ERROR = "CLIENT_ERROR",
11
+ UNKNOWN_ERROR = "UNKNOWN_ERROR"
12
+ }
13
+ declare const USER_FRIENDLY_ERROR_MESSAGES: Readonly<Record<AIErrorType, string>>;
14
+
15
+ interface AIErrorMetadata {
16
+ readonly status?: number;
17
+ readonly url?: string;
18
+ readonly [key: string]: unknown;
19
+ }
20
+ declare class AIError extends Error {
21
+ readonly type: AIErrorType;
22
+ readonly code: string;
23
+ readonly cause?: unknown;
24
+ readonly metadata: AIErrorMetadata;
25
+ constructor(typeOrMessage: AIErrorType | string, messageOrCode?: string, cause?: unknown, metadata?: AIErrorMetadata);
26
+ static from(error: unknown, fallbackCode?: string): AIError;
27
+ toString(): string;
28
+ toJSON(): {
29
+ name: string;
30
+ type: AIErrorType;
31
+ code: string;
32
+ message: string;
33
+ metadata: AIErrorMetadata;
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Per-provider default model per tier. Config can override any slot; an
39
+ * explicit `model` on a request overrides everything. This table is the knob
40
+ * that turns "switch from Claude to Groq" into a config edit.
41
+ */
42
+ type TierModelMap = Readonly<Partial<Record<TextTier, string>>>;
43
+ declare const TEXT_TIERS: ReadonlyArray<TextTier>;
44
+ declare function isTextTier(value: unknown): value is TextTier;
45
+ declare function resolveModel(tiers: TierModelMap, tier: TextTier, providerId: string): string;
46
+ declare function mergeTierOverrides(defaults: TierModelMap, overrides?: TierModelMap): TierModelMap;
47
+
48
+ export { AIError as A, TEXT_TIERS as T, USER_FRIENDLY_ERROR_MESSAGES as U, type AIErrorMetadata as a, AIErrorType as b, type TierModelMap as c, isTextTier as i, mergeTierOverrides as m, resolveModel as r };
@@ -0,0 +1,48 @@
1
+ import { c as TextTier } from './index-fXjxTR5W.cjs';
2
+
3
+ declare enum AIErrorType {
4
+ INVALID_API_KEY = "INVALID_API_KEY",
5
+ MISSING_CONFIG = "MISSING_CONFIG",
6
+ NETWORK_ERROR = "NETWORK_ERROR",
7
+ ABORT_ERROR = "ABORT_ERROR",
8
+ RATE_LIMIT_ERROR = "RATE_LIMIT_ERROR",
9
+ SERVER_ERROR = "SERVER_ERROR",
10
+ CLIENT_ERROR = "CLIENT_ERROR",
11
+ UNKNOWN_ERROR = "UNKNOWN_ERROR"
12
+ }
13
+ declare const USER_FRIENDLY_ERROR_MESSAGES: Readonly<Record<AIErrorType, string>>;
14
+
15
+ interface AIErrorMetadata {
16
+ readonly status?: number;
17
+ readonly url?: string;
18
+ readonly [key: string]: unknown;
19
+ }
20
+ declare class AIError extends Error {
21
+ readonly type: AIErrorType;
22
+ readonly code: string;
23
+ readonly cause?: unknown;
24
+ readonly metadata: AIErrorMetadata;
25
+ constructor(typeOrMessage: AIErrorType | string, messageOrCode?: string, cause?: unknown, metadata?: AIErrorMetadata);
26
+ static from(error: unknown, fallbackCode?: string): AIError;
27
+ toString(): string;
28
+ toJSON(): {
29
+ name: string;
30
+ type: AIErrorType;
31
+ code: string;
32
+ message: string;
33
+ metadata: AIErrorMetadata;
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Per-provider default model per tier. Config can override any slot; an
39
+ * explicit `model` on a request overrides everything. This table is the knob
40
+ * that turns "switch from Claude to Groq" into a config edit.
41
+ */
42
+ type TierModelMap = Readonly<Partial<Record<TextTier, string>>>;
43
+ declare const TEXT_TIERS: ReadonlyArray<TextTier>;
44
+ declare function isTextTier(value: unknown): value is TextTier;
45
+ declare function resolveModel(tiers: TierModelMap, tier: TextTier, providerId: string): string;
46
+ declare function mergeTierOverrides(defaults: TierModelMap, overrides?: TierModelMap): TierModelMap;
47
+
48
+ export { AIError as A, TEXT_TIERS as T, USER_FRIENDLY_ERROR_MESSAGES as U, type AIErrorMetadata as a, AIErrorType as b, type TierModelMap as c, isTextTier as i, mergeTierOverrides as m, resolveModel as r };
package/dist/index.cjs CHANGED
@@ -43,17 +43,80 @@ var USER_FRIENDLY_ERROR_MESSAGES = {
43
43
  };
44
44
 
45
45
  // src/core/errors/ai-error.ts
46
- var AIError = class extends Error {
46
+ var KNOWN_ERROR_TYPES = new Set(Object.values(AIErrorType));
47
+ function mapCodeToType(code) {
48
+ switch (code) {
49
+ case "VALIDATION_ERROR":
50
+ case "CLIENT_ERROR":
51
+ return "CLIENT_ERROR" /* CLIENT_ERROR */;
52
+ case "INVALID_API_KEY":
53
+ return "INVALID_API_KEY" /* INVALID_API_KEY */;
54
+ case "MISSING_CONFIG":
55
+ case "PROVIDER_UNAVAILABLE":
56
+ return "MISSING_CONFIG" /* MISSING_CONFIG */;
57
+ case "PROVIDER_QUOTA_EXHAUSTED":
58
+ case "RATE_LIMIT_ERROR":
59
+ return "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */;
60
+ case "NETWORK_ERROR":
61
+ case "TIMEOUT":
62
+ return "NETWORK_ERROR" /* NETWORK_ERROR */;
63
+ case "ABORT_ERROR":
64
+ case "ABORTED":
65
+ return "ABORT_ERROR" /* ABORT_ERROR */;
66
+ case "SERVER_ERROR":
67
+ case "PROVIDER_ERROR":
68
+ case "NO_RESULT":
69
+ return "SERVER_ERROR" /* SERVER_ERROR */;
70
+ default:
71
+ return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
72
+ }
73
+ }
74
+ function mapMessageToCode(message, fallback = "UNKNOWN") {
75
+ const lower = message.toLowerCase();
76
+ if (lower.includes("validation")) return "VALIDATION_ERROR";
77
+ if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
78
+ if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
79
+ if (lower.includes("timeout") || lower.includes("aborted")) {
80
+ return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
81
+ }
82
+ if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
83
+ if (lower.includes("no result")) return "NO_RESULT";
84
+ if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
85
+ return fallback;
86
+ }
87
+ var AIError = class _AIError extends Error {
47
88
  type;
89
+ code;
48
90
  cause;
49
91
  metadata;
50
- constructor(type, message, cause, metadata = {}) {
51
- super(message);
92
+ constructor(typeOrMessage, messageOrCode, cause, metadata = {}) {
93
+ let effectiveType;
94
+ let effectiveMessage;
95
+ let effectiveCode;
96
+ if (KNOWN_ERROR_TYPES.has(typeOrMessage)) {
97
+ effectiveType = typeOrMessage;
98
+ effectiveMessage = messageOrCode ?? "";
99
+ effectiveCode = effectiveType;
100
+ } else {
101
+ effectiveMessage = typeOrMessage;
102
+ effectiveCode = messageOrCode ?? "UNKNOWN";
103
+ effectiveType = mapCodeToType(effectiveCode);
104
+ }
105
+ super(effectiveMessage);
52
106
  this.name = "AIError";
53
- this.type = type;
107
+ this.type = effectiveType;
108
+ this.code = effectiveCode;
54
109
  this.cause = cause;
55
110
  this.metadata = metadata;
56
111
  }
112
+ static from(error, fallbackCode = "UNKNOWN") {
113
+ if (error instanceof _AIError) return error;
114
+ if (error instanceof Error) {
115
+ const code = mapMessageToCode(error.message, fallbackCode);
116
+ return new _AIError(error.message, code, error);
117
+ }
118
+ return new _AIError(String(error), fallbackCode, error);
119
+ }
57
120
  toString() {
58
121
  const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
59
122
  return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
@@ -62,6 +125,7 @@ var AIError = class extends Error {
62
125
  return {
63
126
  name: this.name,
64
127
  type: this.type,
128
+ code: this.code,
65
129
  message: this.message,
66
130
  metadata: this.metadata
67
131
  };
@@ -750,15 +814,22 @@ function oneOfCatalog(catalog, value) {
750
814
  // src/core/constants/protocol.constants.ts
751
815
  var SSE_PREFIX = "data: ";
752
816
  var SSE_DONE_MARKER = "[DONE]";
753
- var JSON_CODE_BLOCK_PATTERN = /```json\n([\s\S]*?)\n```/;
817
+ var JSON_CODE_BLOCK_PATTERN = /```(?:json)?\s*\n?([\s\S]*?)\n?```/;
754
818
  var JSON_OBJECT_PATTERN = /\{[\s\S]*\}/;
819
+ var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
755
820
 
756
821
  // src/core/parsers/sse-lines.ts
757
822
  function isSseDataLine(line) {
758
- return line.startsWith(SSE_PREFIX);
823
+ return line.startsWith(SSE_PREFIX) || line.startsWith("data:");
759
824
  }
760
825
  function extractSseDataPayload(line) {
761
- return line.slice(SSE_PREFIX.length);
826
+ if (line.startsWith(SSE_PREFIX)) {
827
+ return line.slice(SSE_PREFIX.length);
828
+ }
829
+ if (line.startsWith("data:")) {
830
+ return line.slice(5);
831
+ }
832
+ return line;
762
833
  }
763
834
  function isSseDoneMarker(payload) {
764
835
  return payload === SSE_DONE_MARKER;
@@ -830,7 +901,7 @@ var StreamingResponseParser = class {
830
901
  // src/core/parsers/json-content-extractor.ts
831
902
  function extractJsonFromCodeFence(content) {
832
903
  const match = content.match(JSON_CODE_BLOCK_PATTERN);
833
- if (match && match[1]) return match[1];
904
+ if (match && match[1]) return match[1].trim();
834
905
  return null;
835
906
  }
836
907
  function extractJsonObject(content) {
@@ -838,8 +909,27 @@ function extractJsonObject(content) {
838
909
  if (match && match[0]) return match[0];
839
910
  return null;
840
911
  }
912
+ function extractJsonArray(content) {
913
+ const match = content.match(JSON_ARRAY_PATTERN);
914
+ if (match && match[0]) return match[0];
915
+ return null;
916
+ }
917
+ function repairCommonJsonDefects(text) {
918
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
919
+ }
841
920
  function extractJsonCandidate(content) {
842
- return extractJsonFromCodeFence(content) ?? extractJsonObject(content) ?? content;
921
+ const fromFence = extractJsonFromCodeFence(content);
922
+ if (fromFence) return fromFence;
923
+ const objectIndex = content.indexOf("{");
924
+ const arrayIndex = content.indexOf("[");
925
+ if (objectIndex !== -1 && (arrayIndex === -1 || objectIndex < arrayIndex)) {
926
+ const obj = extractJsonObject(content);
927
+ if (obj) return obj;
928
+ } else if (arrayIndex !== -1) {
929
+ const arr = extractJsonArray(content);
930
+ if (arr) return arr;
931
+ }
932
+ return content.trim();
843
933
  }
844
934
 
845
935
  // src/core/parsers/json-response.parser.ts
@@ -848,12 +938,17 @@ var JsonResponseParser = class {
848
938
  const candidate = extractJsonCandidate(rawContent);
849
939
  try {
850
940
  return JSON.parse(candidate);
851
- } catch (error) {
852
- throw new AIError(
853
- "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
854
- "Failed to parse structured response as JSON",
855
- error instanceof Error ? error : void 0
856
- );
941
+ } catch {
942
+ try {
943
+ return JSON.parse(repairCommonJsonDefects(candidate));
944
+ } catch (error) {
945
+ throw new AIError(
946
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
947
+ "Failed to parse structured response as JSON",
948
+ error instanceof Error ? error : void 0,
949
+ { rawContent }
950
+ );
951
+ }
857
952
  }
858
953
  }
859
954
  };
@@ -979,38 +1074,6 @@ function isMediaProvider(provider) {
979
1074
  return provider.capabilities.includes("image") || provider.capabilities.includes("video");
980
1075
  }
981
1076
 
982
- // src/content/domain/errors/AIErrors.ts
983
- var AIError2 = class _AIError extends Error {
984
- code;
985
- cause;
986
- constructor(message, code, cause) {
987
- super(message);
988
- this.name = "AIError";
989
- this.code = code;
990
- this.cause = cause;
991
- }
992
- static from(error, fallbackCode = "UNKNOWN") {
993
- if (error instanceof _AIError) return error;
994
- if (error instanceof Error) {
995
- return new _AIError(error.message, mapMessageToCode(error.message, fallbackCode), error);
996
- }
997
- return new _AIError(String(error), fallbackCode, error);
998
- }
999
- };
1000
- function mapMessageToCode(message, fallback) {
1001
- const lower = message.toLowerCase();
1002
- if (lower.includes("validation")) return "VALIDATION_ERROR";
1003
- if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
1004
- if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
1005
- if (lower.includes("timeout") || lower.includes("aborted")) {
1006
- return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
1007
- }
1008
- if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
1009
- if (lower.includes("no result")) return "NO_RESULT";
1010
- if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
1011
- return fallback;
1012
- }
1013
-
1014
1077
  // src/content/domain/limits/ProviderTimingConfig.ts
1015
1078
  var ProviderTimingConfig = {
1016
1079
  DEFAULT_TIMEOUT_MS: 3e4,
@@ -1075,7 +1138,7 @@ var BaseProvider = class {
1075
1138
  throw lastError || new Error("Retry attempts exhausted");
1076
1139
  }
1077
1140
  };
1078
- var ProviderError = class extends AIError2 {
1141
+ var ProviderError = class extends AIError {
1079
1142
  constructor(message, providerId, originalError) {
1080
1143
  super(message, "PROVIDER_ERROR", originalError);
1081
1144
  this.providerId = providerId;
@@ -1169,12 +1232,16 @@ var ProviderFactory = class {
1169
1232
  let wake = null;
1170
1233
  let finished = false;
1171
1234
  let failure;
1235
+ const controller = new AbortController();
1236
+ const onAbort = () => controller.abort();
1237
+ request.signal?.addEventListener("abort", onAbort);
1238
+ const effectiveRequest = { ...request, signal: controller.signal };
1172
1239
  const notify = () => {
1173
1240
  const resolve = wake;
1174
1241
  wake = null;
1175
1242
  resolve?.();
1176
1243
  };
1177
- const completion = this.chatStream(request, {
1244
+ const completion = this.chatStream(effectiveRequest, {
1178
1245
  onChunk: (chunk) => {
1179
1246
  queue.push(chunk);
1180
1247
  notify();
@@ -1190,18 +1257,23 @@ var ProviderFactory = class {
1190
1257
  notify();
1191
1258
  }
1192
1259
  );
1193
- while (!finished || queue.length > 0) {
1194
- if (queue.length === 0 && !finished) {
1195
- await new Promise((resolve) => {
1196
- wake = resolve;
1197
- });
1198
- }
1199
- while (queue.length > 0) {
1200
- yield queue.shift();
1260
+ try {
1261
+ while (!finished || queue.length > 0) {
1262
+ if (queue.length === 0 && !finished) {
1263
+ await new Promise((resolve) => {
1264
+ wake = resolve;
1265
+ });
1266
+ }
1267
+ while (queue.length > 0) {
1268
+ yield queue.shift();
1269
+ }
1270
+ if (failure) throw failure;
1201
1271
  }
1202
- if (failure) throw failure;
1272
+ await completion;
1273
+ } finally {
1274
+ controller.abort();
1275
+ request.signal?.removeEventListener("abort", onAbort);
1203
1276
  }
1204
- await completion;
1205
1277
  }
1206
1278
  async generateJson(request) {
1207
1279
  const effective = request.model || request.tier ? request : { ...request, tier: this.defaultTier };
@@ -1610,11 +1682,14 @@ async function consumeAnthropicStream(stream, callbacks) {
1610
1682
  break;
1611
1683
  case "message_stop":
1612
1684
  break;
1613
- case "error":
1685
+ case "error": {
1686
+ const errType = event.error.type;
1687
+ const mappedType = errType === "rate_limit_error" ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : errType === "authentication_error" ? "INVALID_API_KEY" /* INVALID_API_KEY */ : errType === "invalid_request_error" ? "CLIENT_ERROR" /* CLIENT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */;
1614
1688
  throw new AIError(
1615
- event.error.type === "rate_limit_error" ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
1689
+ mappedType,
1616
1690
  event.error.message || "Anthropic stream failed mid-flight"
1617
1691
  );
1692
+ }
1618
1693
  }
1619
1694
  };
1620
1695
  try {
@@ -2072,6 +2147,13 @@ async function consumeGroqStream(stream, callbacks) {
2072
2147
  } catch {
2073
2148
  return;
2074
2149
  }
2150
+ const errorBody = chunk.error;
2151
+ if (errorBody) {
2152
+ throw new AIError(
2153
+ errorBody.message?.toLowerCase().includes("rate limit") ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
2154
+ errorBody.message || "Groq stream failed mid-flight"
2155
+ );
2156
+ }
2075
2157
  if (chunk.model) model = chunk.model;
2076
2158
  const choice = chunk.choices?.[0];
2077
2159
  const delta = choice?.delta?.content;
@@ -2086,13 +2168,6 @@ async function consumeGroqStream(stream, callbacks) {
2086
2168
  if (tailUsage) {
2087
2169
  usage = mapGroqUsage(tailUsage);
2088
2170
  }
2089
- const errorBody = chunk.error;
2090
- if (errorBody) {
2091
- throw new AIError(
2092
- "SERVER_ERROR" /* SERVER_ERROR */,
2093
- errorBody.message || "Groq stream failed mid-flight"
2094
- );
2095
- }
2096
2171
  };
2097
2172
  try {
2098
2173
  while (true) {
@@ -2359,7 +2434,9 @@ var GeminiProvider = class extends BaseProvider {
2359
2434
  headers: this.authHeaders()
2360
2435
  }
2361
2436
  );
2362
- return response.ok ? "healthy" : "unavailable";
2437
+ if (response.ok) return "healthy";
2438
+ if (response.status === 401 || response.status === 403) return "unavailable";
2439
+ return "degraded";
2363
2440
  } catch {
2364
2441
  return "unavailable";
2365
2442
  }
@@ -2370,6 +2447,8 @@ var GeminiProvider = class extends BaseProvider {
2370
2447
  async generate(request) {
2371
2448
  try {
2372
2449
  const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
2450
+ const systemMessages = request.messages.filter((m) => m.role === "system");
2451
+ const systemPrompt = request.systemPrompt && request.systemPrompt.length > 0 ? request.systemPrompt : systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n") : void 0;
2373
2452
  const response = await this.withRetry(async () => {
2374
2453
  return await this.fetchWithTimeout(
2375
2454
  `${this.config.baseUrl}/models/${model}:generateContent`,
@@ -2377,22 +2456,28 @@ var GeminiProvider = class extends BaseProvider {
2377
2456
  method: "POST",
2378
2457
  headers: this.authHeaders(),
2379
2458
  body: JSON.stringify({
2380
- systemInstruction: request.systemPrompt && request.systemPrompt.length > 0 ? { parts: [{ text: request.systemPrompt }] } : void 0,
2459
+ systemInstruction: systemPrompt && systemPrompt.length > 0 ? { parts: [{ text: systemPrompt }] } : void 0,
2381
2460
  contents: toGeminiContents(request.messages),
2382
2461
  generationConfig: {
2383
- temperature: request.temperature || 0.7,
2384
- maxOutputTokens: request.maxTokens || 2048,
2385
- topP: request.topP || 0.9
2462
+ temperature: request.temperature ?? 0.7,
2463
+ maxOutputTokens: request.maxTokens ?? 2048,
2464
+ topP: request.topP ?? 0.9
2386
2465
  }
2387
2466
  })
2388
2467
  }
2389
2468
  );
2390
2469
  });
2391
2470
  if (!response.ok) {
2392
- throw new Error(`Gemini API error: ${response.statusText}`);
2471
+ const message = await extractErrorMessageFromResponse(response);
2472
+ throw new AIError(
2473
+ classifyHttpStatus(response.status),
2474
+ `Gemini API error (${response.status}): ${message}`,
2475
+ void 0,
2476
+ { status: response.status, model }
2477
+ );
2393
2478
  }
2394
2479
  const data = await response.json();
2395
- const text = data.candidates[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
2480
+ const text = data.candidates?.[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
2396
2481
  if (text.length === 0) {
2397
2482
  throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No content generated from Gemini API", void 0, { model });
2398
2483
  }
@@ -2405,7 +2490,7 @@ var GeminiProvider = class extends BaseProvider {
2405
2490
  } : void 0,
2406
2491
  model,
2407
2492
  providerId: this.id,
2408
- finishReason: mapGeminiFinishReason(data.candidates[0]?.finishReason)
2493
+ finishReason: mapGeminiFinishReason(data.candidates?.[0]?.finishReason)
2409
2494
  };
2410
2495
  } catch (error) {
2411
2496
  if (error instanceof AIError) throw error;
@@ -2457,12 +2542,18 @@ var GeminiProvider = class extends BaseProvider {
2457
2542
  );
2458
2543
  });
2459
2544
  if (!response.ok) {
2460
- throw new Error(`Gemini API error: ${response.statusText}`);
2545
+ const message = await extractErrorMessageFromResponse(response);
2546
+ throw new AIError(
2547
+ classifyHttpStatus(response.status),
2548
+ `Gemini image generation error (${response.status}): ${message}`,
2549
+ void 0,
2550
+ { status: response.status }
2551
+ );
2461
2552
  }
2462
2553
  const data = await response.json();
2463
- const imageData = data.candidates[0]?.content?.parts[0]?.inlineData;
2554
+ const imageData = data.candidates?.[0]?.content?.parts?.[0]?.inlineData;
2464
2555
  if (!imageData) {
2465
- throw new Error("No image data in response");
2556
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image data in Gemini response");
2466
2557
  }
2467
2558
  const url = `data:${imageData.mimeType};base64,${imageData.data}`;
2468
2559
  return {
@@ -2781,17 +2872,70 @@ var PrunaProvider = class {
2781
2872
  async estimateCost(_request) {
2782
2873
  return 1;
2783
2874
  }
2784
- async generateImage(_request) {
2785
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2875
+ async generateImage(request) {
2876
+ let finalUrl;
2877
+ const model = this.config.models?.textToImage;
2878
+ for await (const stage of generateWithPruna(
2879
+ { prompt: request.prompt, model, quality: request.quality },
2880
+ this.config.apiKey
2881
+ )) {
2882
+ if (stage.error) throw stage.error;
2883
+ if (stage.result?.url) finalUrl = stage.result.url;
2884
+ }
2885
+ if (!finalUrl) {
2886
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image generated by Pruna AI");
2887
+ }
2888
+ return {
2889
+ id: Date.now().toString(),
2890
+ type: "image",
2891
+ url: finalUrl,
2892
+ metadata: { provider: this.id, model: model ?? "p-image" },
2893
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2894
+ };
2786
2895
  }
2787
- async generateVideo(_request) {
2788
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2896
+ async generateVideo(request) {
2897
+ let finalUrl;
2898
+ const model = this.config.models?.textToVideo;
2899
+ for await (const stage of generateWithPruna(
2900
+ { prompt: request.prompt, model, duration: request.duration, aspectRatio: request.aspectRatio },
2901
+ this.config.apiKey
2902
+ )) {
2903
+ if (stage.error) throw stage.error;
2904
+ if (stage.result?.url) finalUrl = stage.result.url;
2905
+ }
2906
+ if (!finalUrl) {
2907
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
2908
+ }
2909
+ return {
2910
+ id: Date.now().toString(),
2911
+ type: "video",
2912
+ url: finalUrl,
2913
+ metadata: { provider: this.id, model: model ?? "p-video" },
2914
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2915
+ };
2789
2916
  }
2790
- async imageToVideo(_request) {
2791
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2917
+ async imageToVideo(request) {
2918
+ let finalUrl;
2919
+ for await (const stage of generateWithPruna(
2920
+ { image: request.imageUrl, prompt: request.prompt, duration: request.duration, motion: request.motion },
2921
+ this.config.apiKey
2922
+ )) {
2923
+ if (stage.error) throw stage.error;
2924
+ if (stage.result?.url) finalUrl = stage.result.url;
2925
+ }
2926
+ if (!finalUrl) {
2927
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated from image by Pruna AI");
2928
+ }
2929
+ return {
2930
+ id: Date.now().toString(),
2931
+ type: "video",
2932
+ url: finalUrl,
2933
+ metadata: { provider: this.id, model: "p-image-to-video" },
2934
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2935
+ };
2792
2936
  }
2793
2937
  async videoToVideo(_request) {
2794
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2938
+ throw new AIError("CLIENT_ERROR" /* CLIENT_ERROR */, "Pruna AI provider does not support video-to-video conversion");
2795
2939
  }
2796
2940
  };
2797
2941
  function createPrunaProvider(config) {
@@ -2943,9 +3087,12 @@ var ChatService = class {
2943
3087
  };
2944
3088
  }
2945
3089
  async getConversations() {
3090
+ if (this.storage && "getConversations" in this.storage && typeof this.storage.getConversations === "function") {
3091
+ return this.storage.getConversations();
3092
+ }
2946
3093
  return [];
2947
3094
  }
2948
- async sendMessage(input) {
3095
+ async sendMessage(input, signal) {
2949
3096
  const userMessage = {
2950
3097
  ...messageFormatter.toChatMessage(input.text, "user"),
2951
3098
  type: input.type ?? "text",
@@ -2954,13 +3101,14 @@ var ChatService = class {
2954
3101
  const aiResponse = await this.generateAIResponse(
2955
3102
  input.companionId,
2956
3103
  input.text,
2957
- []
3104
+ [],
3105
+ signal
2958
3106
  );
2959
3107
  return { userMessage, aiResponse };
2960
3108
  }
2961
- async generateAIResponse(companionId, userMessage, context = []) {
3109
+ async generateAIResponse(companionId, userMessage, context = [], signal) {
2962
3110
  const result = await this.run({
2963
- ...this.buildRequest(userMessage, context)
3111
+ ...this.buildRequest(userMessage, context, signal)
2964
3112
  });
2965
3113
  return {
2966
3114
  ...messageFormatter.toChatMessage(result.text, "assistant"),
@@ -2976,12 +3124,12 @@ var ChatService = class {
2976
3124
  }
2977
3125
  };
2978
3126
  }
2979
- async generateStreamingResponse(userMessage, context, onChunk) {
2980
- const request = this.buildRequest(userMessage, context);
3127
+ async generateStreamingResponse(userMessage, context, onChunk, signal) {
3128
+ const request = this.buildRequest(userMessage, context, signal);
2981
3129
  const result = await this.runStream(request, { onChunk });
2982
3130
  return result.text;
2983
3131
  }
2984
- buildRequest(userMessage, context) {
3132
+ buildRequest(userMessage, context, signal) {
2985
3133
  const messages = messageFormatter.toCoreMessages(context);
2986
3134
  messages.push({ role: "user", content: userMessage });
2987
3135
  return {
@@ -2990,7 +3138,8 @@ var ChatService = class {
2990
3138
  model: this.config.model,
2991
3139
  tier: this.config.tier,
2992
3140
  temperature: this.config.temperature,
2993
- maxTokens: this.config.maxTokens
3141
+ maxTokens: this.config.maxTokens,
3142
+ ...signal !== void 0 ? { signal } : {}
2994
3143
  };
2995
3144
  }
2996
3145
  resolveSystemPrompt() {
@@ -3122,6 +3271,7 @@ exports.describeAIErrorType = describeAIErrorType;
3122
3271
  exports.extractContentFromSsePayload = extractContentFromSsePayload;
3123
3272
  exports.extractErrorMessageFromJsonBody = extractErrorMessageFromJsonBody;
3124
3273
  exports.extractErrorMessageFromResponse = extractErrorMessageFromResponse;
3274
+ exports.extractJsonArray = extractJsonArray;
3125
3275
  exports.extractJsonCandidate = extractJsonCandidate;
3126
3276
  exports.extractJsonFromCodeFence = extractJsonFromCodeFence;
3127
3277
  exports.extractJsonObject = extractJsonObject;
@@ -3142,6 +3292,7 @@ exports.mergeTierOverrides = mergeTierOverrides;
3142
3292
  exports.messageFormatter = messageFormatter;
3143
3293
  exports.normalizeSseChunk = normalizeSseChunk;
3144
3294
  exports.oneOfCatalog = oneOfCatalog;
3295
+ exports.repairCommonJsonDefects = repairCommonJsonDefects;
3145
3296
  exports.requestDeduplicator = requestDeduplicator;
3146
3297
  exports.requestQueue = requestQueue;
3147
3298
  exports.resolveModel = resolveModel;