@umituz/web-ai 1.0.0 → 1.0.2
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/{ABTesting-DDqNhHtP.d.ts → AIErrors-B2ogd1PB.d.ts} +14 -1
- package/dist/{ABTesting-DWBtKTMQ.d.cts → AIErrors-C5EasV_c.d.cts} +14 -1
- package/dist/{ProviderConfig-a55tLhWT.d.cts → ProviderConfig-DIFa9nmG.d.cts} +10 -1
- package/dist/{ProviderConfig-a55tLhWT.d.ts → ProviderConfig-DIFa9nmG.d.ts} +10 -1
- package/dist/chat/index.cjs +114 -23
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +4 -4
- package/dist/chat/index.d.ts +4 -4
- package/dist/chat/index.js +114 -23
- package/dist/chat/index.js.map +1 -1
- package/dist/{chat.service-DBKq9I2c.d.cts → chat.service-CL1qtYxe.d.cts} +7 -7
- package/dist/{chat.service-RQn8UTXD.d.ts → chat.service-q05GnhEm.d.ts} +7 -7
- package/dist/content/index.cjs +506 -193
- package/dist/content/index.cjs.map +1 -1
- package/dist/content/index.d.cts +7 -8
- package/dist/content/index.d.ts +7 -8
- package/dist/content/index.js +506 -193
- package/dist/content/index.js.map +1 -1
- package/dist/core/index.cjs +122 -15
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +13 -31
- package/dist/core/index.d.ts +13 -31
- package/dist/core/index.js +119 -16
- package/dist/core/index.js.map +1 -1
- package/dist/{create-ai-BhNaz86i.d.ts → create-ai-9PW752z8.d.ts} +14 -21
- package/dist/{create-ai-DGZFBOA1.d.cts → create-ai-CXYKPK6G.d.cts} +14 -21
- package/dist/hooks/index.cjs +631 -179
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +33 -7
- package/dist/hooks/index.d.ts +33 -7
- package/dist/hooks/index.js +631 -180
- package/dist/hooks/index.js.map +1 -1
- package/dist/index-DIHz8_OJ.d.ts +48 -0
- package/dist/index-DKHNcMbl.d.cts +48 -0
- package/dist/index.cjs +666 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +645 -105
- package/dist/index.js.map +1 -1
- package/dist/{provider.factory-COsnprLH.d.cts → provider.factory-BxsfK4WW.d.cts} +1 -1
- package/dist/{provider.factory-CpWFyjbw.d.ts → provider.factory-DfDfk0dK.d.ts} +1 -1
- package/dist/providers/index.cjs +973 -130
- package/dist/providers/index.cjs.map +1 -1
- package/dist/providers/index.d.cts +84 -18
- package/dist/providers/index.d.ts +84 -18
- package/dist/providers/index.js +956 -131
- package/dist/providers/index.js.map +1 -1
- package/dist/text-generation.service-BQO3Tvz2.d.cts +66 -0
- package/dist/text-generation.service-CcYQM6Ch.d.ts +66 -0
- package/dist/wizard/index.d.cts +1 -1
- package/dist/wizard/index.d.ts +1 -1
- package/package.json +9 -1
- package/dist/index-DjX9ZKuM.d.cts +0 -14
- package/dist/index-DxLvE588.d.ts +0 -14
package/dist/content/index.cjs
CHANGED
|
@@ -75,37 +75,110 @@ var VIDEO_DURATION_OPTIONS = VIDEO_DURATION_OPTIONS_SECONDS;
|
|
|
75
75
|
var getDimensionsForAspectRatio = resolveDimensionsForAspectRatio;
|
|
76
76
|
var getRecommendedQuality = recommendQualityForAspectRatio;
|
|
77
77
|
|
|
78
|
-
// src/
|
|
78
|
+
// src/core/errors/ai-error-types.ts
|
|
79
|
+
var AIErrorType = /* @__PURE__ */ ((AIErrorType2) => {
|
|
80
|
+
AIErrorType2["INVALID_API_KEY"] = "INVALID_API_KEY";
|
|
81
|
+
AIErrorType2["MISSING_CONFIG"] = "MISSING_CONFIG";
|
|
82
|
+
AIErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
83
|
+
AIErrorType2["ABORT_ERROR"] = "ABORT_ERROR";
|
|
84
|
+
AIErrorType2["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR";
|
|
85
|
+
AIErrorType2["SERVER_ERROR"] = "SERVER_ERROR";
|
|
86
|
+
AIErrorType2["CLIENT_ERROR"] = "CLIENT_ERROR";
|
|
87
|
+
AIErrorType2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
88
|
+
return AIErrorType2;
|
|
89
|
+
})(AIErrorType || {});
|
|
90
|
+
|
|
91
|
+
// src/core/errors/ai-error.ts
|
|
92
|
+
var KNOWN_ERROR_TYPES = new Set(Object.values(AIErrorType));
|
|
93
|
+
function mapCodeToType(code) {
|
|
94
|
+
switch (code) {
|
|
95
|
+
case "VALIDATION_ERROR":
|
|
96
|
+
case "CLIENT_ERROR":
|
|
97
|
+
return "CLIENT_ERROR" /* CLIENT_ERROR */;
|
|
98
|
+
case "INVALID_API_KEY":
|
|
99
|
+
return "INVALID_API_KEY" /* INVALID_API_KEY */;
|
|
100
|
+
case "MISSING_CONFIG":
|
|
101
|
+
case "PROVIDER_UNAVAILABLE":
|
|
102
|
+
return "MISSING_CONFIG" /* MISSING_CONFIG */;
|
|
103
|
+
case "PROVIDER_QUOTA_EXHAUSTED":
|
|
104
|
+
case "RATE_LIMIT_ERROR":
|
|
105
|
+
return "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */;
|
|
106
|
+
case "NETWORK_ERROR":
|
|
107
|
+
case "TIMEOUT":
|
|
108
|
+
return "NETWORK_ERROR" /* NETWORK_ERROR */;
|
|
109
|
+
case "ABORT_ERROR":
|
|
110
|
+
case "ABORTED":
|
|
111
|
+
return "ABORT_ERROR" /* ABORT_ERROR */;
|
|
112
|
+
case "SERVER_ERROR":
|
|
113
|
+
case "PROVIDER_ERROR":
|
|
114
|
+
case "NO_RESULT":
|
|
115
|
+
return "SERVER_ERROR" /* SERVER_ERROR */;
|
|
116
|
+
default:
|
|
117
|
+
return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function mapMessageToCode(message, fallback = "UNKNOWN") {
|
|
121
|
+
const lower = message.toLowerCase();
|
|
122
|
+
if (lower.includes("validation")) return "VALIDATION_ERROR";
|
|
123
|
+
if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
|
|
124
|
+
if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
|
|
125
|
+
if (lower.includes("timeout") || lower.includes("aborted")) {
|
|
126
|
+
return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
|
|
127
|
+
}
|
|
128
|
+
if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
|
|
129
|
+
if (lower.includes("no result")) return "NO_RESULT";
|
|
130
|
+
if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
|
|
131
|
+
return fallback;
|
|
132
|
+
}
|
|
79
133
|
var AIError = class _AIError extends Error {
|
|
134
|
+
type;
|
|
80
135
|
code;
|
|
81
136
|
cause;
|
|
82
|
-
|
|
83
|
-
|
|
137
|
+
metadata;
|
|
138
|
+
constructor(typeOrMessage, messageOrCode, cause, metadata = {}) {
|
|
139
|
+
let effectiveType;
|
|
140
|
+
let effectiveMessage;
|
|
141
|
+
let effectiveCode;
|
|
142
|
+
if (KNOWN_ERROR_TYPES.has(typeOrMessage)) {
|
|
143
|
+
effectiveType = typeOrMessage;
|
|
144
|
+
effectiveMessage = messageOrCode ?? "";
|
|
145
|
+
effectiveCode = effectiveType;
|
|
146
|
+
} else {
|
|
147
|
+
effectiveMessage = typeOrMessage;
|
|
148
|
+
effectiveCode = messageOrCode ?? "UNKNOWN";
|
|
149
|
+
effectiveType = mapCodeToType(effectiveCode);
|
|
150
|
+
}
|
|
151
|
+
super(effectiveMessage);
|
|
84
152
|
this.name = "AIError";
|
|
85
|
-
this.
|
|
153
|
+
this.type = effectiveType;
|
|
154
|
+
this.code = effectiveCode;
|
|
86
155
|
this.cause = cause;
|
|
156
|
+
this.metadata = metadata;
|
|
87
157
|
}
|
|
88
158
|
static from(error, fallbackCode = "UNKNOWN") {
|
|
89
159
|
if (error instanceof _AIError) return error;
|
|
90
160
|
if (error instanceof Error) {
|
|
91
|
-
|
|
161
|
+
const code = mapMessageToCode(error.message, fallbackCode);
|
|
162
|
+
return new _AIError(error.message, code, error);
|
|
92
163
|
}
|
|
93
164
|
return new _AIError(String(error), fallbackCode, error);
|
|
94
165
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (lower.includes("validation")) return "VALIDATION_ERROR";
|
|
99
|
-
if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
|
|
100
|
-
if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
|
|
101
|
-
if (lower.includes("timeout") || lower.includes("aborted")) {
|
|
102
|
-
return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
|
|
166
|
+
toString() {
|
|
167
|
+
const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
|
|
168
|
+
return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
|
|
103
169
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
170
|
+
toJSON() {
|
|
171
|
+
return {
|
|
172
|
+
name: this.name,
|
|
173
|
+
type: this.type,
|
|
174
|
+
code: this.code,
|
|
175
|
+
message: this.message,
|
|
176
|
+
metadata: this.metadata
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// src/content/domain/errors/AIErrors.ts
|
|
109
182
|
var ValidationError = class extends AIError {
|
|
110
183
|
constructor(message) {
|
|
111
184
|
super(message, "VALIDATION_ERROR");
|
|
@@ -125,29 +198,11 @@ var ProviderQuotaExhaustedError = class extends AIError {
|
|
|
125
198
|
}
|
|
126
199
|
};
|
|
127
200
|
|
|
128
|
-
// src/core/errors/
|
|
129
|
-
var
|
|
130
|
-
type;
|
|
131
|
-
cause;
|
|
132
|
-
metadata;
|
|
201
|
+
// src/core/errors/groq-error.ts
|
|
202
|
+
var GroqError = class extends AIError {
|
|
133
203
|
constructor(type, message, cause, metadata = {}) {
|
|
134
|
-
super(message);
|
|
135
|
-
this.name = "
|
|
136
|
-
this.type = type;
|
|
137
|
-
this.cause = cause;
|
|
138
|
-
this.metadata = metadata;
|
|
139
|
-
}
|
|
140
|
-
toString() {
|
|
141
|
-
const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
|
|
142
|
-
return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
|
|
143
|
-
}
|
|
144
|
-
toJSON() {
|
|
145
|
-
return {
|
|
146
|
-
name: this.name,
|
|
147
|
-
type: this.type,
|
|
148
|
-
message: this.message,
|
|
149
|
-
metadata: this.metadata
|
|
150
|
-
};
|
|
204
|
+
super(type, message, cause, metadata);
|
|
205
|
+
this.name = "GroqError";
|
|
151
206
|
}
|
|
152
207
|
};
|
|
153
208
|
|
|
@@ -293,7 +348,7 @@ var ProviderFactory = class {
|
|
|
293
348
|
}
|
|
294
349
|
}
|
|
295
350
|
}
|
|
296
|
-
const failure = new
|
|
351
|
+
const failure = new AIError(
|
|
297
352
|
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
298
353
|
`All providers failed for capability 'text'`,
|
|
299
354
|
lastError instanceof Error ? lastError : void 0
|
|
@@ -307,12 +362,16 @@ var ProviderFactory = class {
|
|
|
307
362
|
let wake = null;
|
|
308
363
|
let finished = false;
|
|
309
364
|
let failure;
|
|
365
|
+
const controller = new AbortController();
|
|
366
|
+
const onAbort = () => controller.abort();
|
|
367
|
+
request.signal?.addEventListener("abort", onAbort);
|
|
368
|
+
const effectiveRequest = { ...request, signal: controller.signal };
|
|
310
369
|
const notify = () => {
|
|
311
370
|
const resolve = wake;
|
|
312
371
|
wake = null;
|
|
313
372
|
resolve?.();
|
|
314
373
|
};
|
|
315
|
-
const completion = this.chatStream(
|
|
374
|
+
const completion = this.chatStream(effectiveRequest, {
|
|
316
375
|
onChunk: (chunk) => {
|
|
317
376
|
queue.push(chunk);
|
|
318
377
|
notify();
|
|
@@ -328,18 +387,23 @@ var ProviderFactory = class {
|
|
|
328
387
|
notify();
|
|
329
388
|
}
|
|
330
389
|
);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
390
|
+
try {
|
|
391
|
+
while (!finished || queue.length > 0) {
|
|
392
|
+
if (queue.length === 0 && !finished) {
|
|
393
|
+
await new Promise((resolve) => {
|
|
394
|
+
wake = resolve;
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
while (queue.length > 0) {
|
|
398
|
+
yield queue.shift();
|
|
399
|
+
}
|
|
400
|
+
if (failure) throw failure;
|
|
339
401
|
}
|
|
340
|
-
|
|
402
|
+
await completion;
|
|
403
|
+
} finally {
|
|
404
|
+
controller.abort();
|
|
405
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
341
406
|
}
|
|
342
|
-
await completion;
|
|
343
407
|
}
|
|
344
408
|
async generateJson(request) {
|
|
345
409
|
const effective = request.model || request.tier ? request : { ...request, tier: this.defaultTier };
|
|
@@ -447,7 +511,7 @@ var ProviderFactory = class {
|
|
|
447
511
|
}
|
|
448
512
|
}
|
|
449
513
|
}
|
|
450
|
-
throw new
|
|
514
|
+
throw new AIError(
|
|
451
515
|
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
452
516
|
`All providers failed for capability '${capability}'`,
|
|
453
517
|
lastError instanceof Error ? lastError : void 0
|
|
@@ -476,7 +540,7 @@ var ProviderFactory = class {
|
|
|
476
540
|
}
|
|
477
541
|
}
|
|
478
542
|
noProviderError(capability) {
|
|
479
|
-
return new
|
|
543
|
+
return new AIError(
|
|
480
544
|
"MISSING_CONFIG" /* MISSING_CONFIG */,
|
|
481
545
|
`No provider registered for capability '${capability}' \u2014 create one with createAI() and include its config block`
|
|
482
546
|
);
|
|
@@ -486,15 +550,22 @@ var ProviderFactory = class {
|
|
|
486
550
|
// src/core/constants/protocol.constants.ts
|
|
487
551
|
var SSE_PREFIX = "data: ";
|
|
488
552
|
var SSE_DONE_MARKER = "[DONE]";
|
|
489
|
-
var JSON_CODE_BLOCK_PATTERN = /```json
|
|
553
|
+
var JSON_CODE_BLOCK_PATTERN = /```(?:json)?\s*\n?([\s\S]*?)\n?```/;
|
|
490
554
|
var JSON_OBJECT_PATTERN = /\{[\s\S]*\}/;
|
|
555
|
+
var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
|
|
491
556
|
|
|
492
557
|
// src/core/parsers/sse-lines.ts
|
|
493
558
|
function isSseDataLine(line) {
|
|
494
|
-
return line.startsWith(SSE_PREFIX);
|
|
559
|
+
return line.startsWith(SSE_PREFIX) || line.startsWith("data:");
|
|
495
560
|
}
|
|
496
561
|
function extractSseDataPayload(line) {
|
|
497
|
-
|
|
562
|
+
if (line.startsWith(SSE_PREFIX)) {
|
|
563
|
+
return line.slice(SSE_PREFIX.length);
|
|
564
|
+
}
|
|
565
|
+
if (line.startsWith("data:")) {
|
|
566
|
+
return line.slice(5);
|
|
567
|
+
}
|
|
568
|
+
return line;
|
|
498
569
|
}
|
|
499
570
|
function isSseDoneMarker(payload) {
|
|
500
571
|
return payload === SSE_DONE_MARKER;
|
|
@@ -503,7 +574,7 @@ function isSseDoneMarker(payload) {
|
|
|
503
574
|
// src/core/parsers/json-content-extractor.ts
|
|
504
575
|
function extractJsonFromCodeFence(content) {
|
|
505
576
|
const match = content.match(JSON_CODE_BLOCK_PATTERN);
|
|
506
|
-
if (match && match[1]) return match[1];
|
|
577
|
+
if (match && match[1]) return match[1].trim();
|
|
507
578
|
return null;
|
|
508
579
|
}
|
|
509
580
|
function extractJsonObject(content) {
|
|
@@ -511,8 +582,27 @@ function extractJsonObject(content) {
|
|
|
511
582
|
if (match && match[0]) return match[0];
|
|
512
583
|
return null;
|
|
513
584
|
}
|
|
585
|
+
function extractJsonArray(content) {
|
|
586
|
+
const match = content.match(JSON_ARRAY_PATTERN);
|
|
587
|
+
if (match && match[0]) return match[0];
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
function repairCommonJsonDefects(text) {
|
|
591
|
+
return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
|
|
592
|
+
}
|
|
514
593
|
function extractJsonCandidate(content) {
|
|
515
|
-
|
|
594
|
+
const fromFence = extractJsonFromCodeFence(content);
|
|
595
|
+
if (fromFence) return fromFence;
|
|
596
|
+
const objectIndex = content.indexOf("{");
|
|
597
|
+
const arrayIndex = content.indexOf("[");
|
|
598
|
+
if (objectIndex !== -1 && (arrayIndex === -1 || objectIndex < arrayIndex)) {
|
|
599
|
+
const obj = extractJsonObject(content);
|
|
600
|
+
if (obj) return obj;
|
|
601
|
+
} else if (arrayIndex !== -1) {
|
|
602
|
+
const arr = extractJsonArray(content);
|
|
603
|
+
if (arr) return arr;
|
|
604
|
+
}
|
|
605
|
+
return content.trim();
|
|
516
606
|
}
|
|
517
607
|
|
|
518
608
|
// src/core/parsers/json-response.parser.ts
|
|
@@ -521,12 +611,17 @@ var JsonResponseParser = class {
|
|
|
521
611
|
const candidate = extractJsonCandidate(rawContent);
|
|
522
612
|
try {
|
|
523
613
|
return JSON.parse(candidate);
|
|
524
|
-
} catch
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
614
|
+
} catch {
|
|
615
|
+
try {
|
|
616
|
+
return JSON.parse(repairCommonJsonDefects(candidate));
|
|
617
|
+
} catch (error) {
|
|
618
|
+
throw new AIError(
|
|
619
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
620
|
+
"Failed to parse structured response as JSON",
|
|
621
|
+
error instanceof Error ? error : void 0,
|
|
622
|
+
{ rawContent }
|
|
623
|
+
);
|
|
624
|
+
}
|
|
530
625
|
}
|
|
531
626
|
}
|
|
532
627
|
};
|
|
@@ -652,7 +747,7 @@ var AnthropicHttpTransport = class {
|
|
|
652
747
|
});
|
|
653
748
|
if (!response.ok) await this.throwFromErrorResponse(response);
|
|
654
749
|
if (!response.body) {
|
|
655
|
-
throw new
|
|
750
|
+
throw new AIError("NETWORK_ERROR" /* NETWORK_ERROR */, "Response body is null");
|
|
656
751
|
}
|
|
657
752
|
return response.body;
|
|
658
753
|
} catch (error) {
|
|
@@ -685,7 +780,7 @@ var AnthropicHttpTransport = class {
|
|
|
685
780
|
status: response.status,
|
|
686
781
|
url: response.url
|
|
687
782
|
};
|
|
688
|
-
throw new
|
|
783
|
+
throw new AIError(
|
|
689
784
|
classifyHttpStatus(response.status),
|
|
690
785
|
errorMessage,
|
|
691
786
|
void 0,
|
|
@@ -693,24 +788,24 @@ var AnthropicHttpTransport = class {
|
|
|
693
788
|
);
|
|
694
789
|
}
|
|
695
790
|
normalizeError(error) {
|
|
696
|
-
if (error instanceof
|
|
791
|
+
if (error instanceof AIError) return error;
|
|
697
792
|
if (error instanceof Error) {
|
|
698
793
|
if (error.name === "AbortError") {
|
|
699
|
-
return new
|
|
794
|
+
return new AIError(
|
|
700
795
|
"ABORT_ERROR" /* ABORT_ERROR */,
|
|
701
796
|
"Request was aborted by the client",
|
|
702
797
|
error
|
|
703
798
|
);
|
|
704
799
|
}
|
|
705
800
|
if (error.name === "TypeError" && error.message.toLowerCase().includes("network")) {
|
|
706
|
-
return new
|
|
801
|
+
return new AIError(
|
|
707
802
|
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
708
803
|
"Network error: Unable to connect to the AI service",
|
|
709
804
|
error
|
|
710
805
|
);
|
|
711
806
|
}
|
|
712
807
|
}
|
|
713
|
-
return new
|
|
808
|
+
return new AIError(
|
|
714
809
|
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
715
810
|
error instanceof Error ? error.message : "Unknown error occurred",
|
|
716
811
|
error instanceof Error ? error : void 0
|
|
@@ -840,11 +935,14 @@ async function consumeAnthropicStream(stream, callbacks) {
|
|
|
840
935
|
break;
|
|
841
936
|
case "message_stop":
|
|
842
937
|
break;
|
|
843
|
-
case "error":
|
|
844
|
-
|
|
845
|
-
|
|
938
|
+
case "error": {
|
|
939
|
+
const errType = event.error.type;
|
|
940
|
+
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 */;
|
|
941
|
+
throw new AIError(
|
|
942
|
+
mappedType,
|
|
846
943
|
event.error.message || "Anthropic stream failed mid-flight"
|
|
847
944
|
);
|
|
945
|
+
}
|
|
848
946
|
}
|
|
849
947
|
};
|
|
850
948
|
try {
|
|
@@ -859,8 +957,8 @@ async function consumeAnthropicStream(stream, callbacks) {
|
|
|
859
957
|
buffer += decoder.decode();
|
|
860
958
|
if (buffer.length > 0) consumeLine(buffer);
|
|
861
959
|
} catch (error) {
|
|
862
|
-
if (error instanceof
|
|
863
|
-
throw new
|
|
960
|
+
if (error instanceof AIError) throw error;
|
|
961
|
+
throw new AIError(
|
|
864
962
|
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
865
963
|
"Stream was interrupted",
|
|
866
964
|
error instanceof Error ? error : void 0
|
|
@@ -922,7 +1020,7 @@ var AnthropicProvider = class {
|
|
|
922
1020
|
const messages = [...request.messages];
|
|
923
1021
|
const lastUserIndex = findLastIndex(messages, (m) => m.role === "user");
|
|
924
1022
|
if (lastUserIndex === -1) {
|
|
925
|
-
throw new
|
|
1023
|
+
throw new AIError(
|
|
926
1024
|
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
927
1025
|
"generateStructured requires at least one user message"
|
|
928
1026
|
);
|
|
@@ -977,7 +1075,7 @@ var AnthropicProvider = class {
|
|
|
977
1075
|
finishReason
|
|
978
1076
|
};
|
|
979
1077
|
}
|
|
980
|
-
throw new
|
|
1078
|
+
throw new AIError(
|
|
981
1079
|
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
982
1080
|
"No content generated from Anthropic API",
|
|
983
1081
|
void 0,
|
|
@@ -1182,7 +1280,7 @@ var RetryManager = class {
|
|
|
1182
1280
|
}
|
|
1183
1281
|
shouldRetry(error, options) {
|
|
1184
1282
|
if (options.shouldRetry) return options.shouldRetry(error);
|
|
1185
|
-
if (error instanceof
|
|
1283
|
+
if (error instanceof AIError) {
|
|
1186
1284
|
return isRetryableErrorType(error.type, DEFAULT_ERROR_CLASSIFICATION_RULE);
|
|
1187
1285
|
}
|
|
1188
1286
|
const lower = error.message.toLowerCase();
|
|
@@ -1304,6 +1402,27 @@ var RequestDeduplicator = class {
|
|
|
1304
1402
|
}
|
|
1305
1403
|
};
|
|
1306
1404
|
|
|
1405
|
+
// src/core/utils/api-key-validator.ts
|
|
1406
|
+
var DEFAULT_API_KEY_RULE = {
|
|
1407
|
+
minLength: 10
|
|
1408
|
+
};
|
|
1409
|
+
function validateApiKey(apiKey, rule = DEFAULT_API_KEY_RULE) {
|
|
1410
|
+
if (!apiKey) {
|
|
1411
|
+
return { valid: false, reason: "API key is required" };
|
|
1412
|
+
}
|
|
1413
|
+
const trimmed = apiKey.trim();
|
|
1414
|
+
if (trimmed.length === 0) {
|
|
1415
|
+
return { valid: false, reason: "API key cannot be empty" };
|
|
1416
|
+
}
|
|
1417
|
+
if (trimmed.length < rule.minLength) {
|
|
1418
|
+
return {
|
|
1419
|
+
valid: false,
|
|
1420
|
+
reason: `API key must be at least ${rule.minLength} characters`
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
return { valid: true };
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1307
1426
|
// src/providers/groq/groq.constants.ts
|
|
1308
1427
|
var GROQ_MODELS = {
|
|
1309
1428
|
LLAMA_3_1_8B_INSTANT: "llama-3.1-8b-instant",
|
|
@@ -1320,11 +1439,13 @@ var DEFAULT_GENERATION_CONFIG = {
|
|
|
1320
1439
|
topP: 1
|
|
1321
1440
|
};
|
|
1322
1441
|
var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
|
|
1442
|
+
var DEFAULT_BASE_URL = GROQ_BASE_URL;
|
|
1323
1443
|
var CHAT_COMPLETIONS_PATH = "/chat/completions";
|
|
1324
1444
|
var GROQ_TIMEOUTS = {
|
|
1325
1445
|
DEFAULT: 3e4,
|
|
1326
1446
|
STREAMING: 6e4
|
|
1327
1447
|
};
|
|
1448
|
+
var TIMEOUTS = GROQ_TIMEOUTS;
|
|
1328
1449
|
var HTTP_HEADERS = {
|
|
1329
1450
|
CONTENT_TYPE: "Content-Type",
|
|
1330
1451
|
APPLICATION_JSON: "application/json",
|
|
@@ -1383,13 +1504,65 @@ var GroqChatRequestBuilder = class _GroqChatRequestBuilder {
|
|
|
1383
1504
|
}
|
|
1384
1505
|
};
|
|
1385
1506
|
|
|
1507
|
+
// src/providers/groq/groq-config-store.ts
|
|
1508
|
+
var GroqConfigStore = class {
|
|
1509
|
+
config = null;
|
|
1510
|
+
initialize(config) {
|
|
1511
|
+
const validation = validateApiKey(config.apiKey, { minLength: 1 });
|
|
1512
|
+
if (!validation.valid) {
|
|
1513
|
+
throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
|
|
1514
|
+
}
|
|
1515
|
+
const normalized = {
|
|
1516
|
+
apiKey: config.apiKey.trim(),
|
|
1517
|
+
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
1518
|
+
timeoutMs: config.timeoutMs ?? TIMEOUTS.DEFAULT,
|
|
1519
|
+
...config.models !== void 0 ? { models: config.models } : {},
|
|
1520
|
+
...config.textModel !== void 0 ? { textModel: config.textModel } : {}
|
|
1521
|
+
};
|
|
1522
|
+
this.config = normalized;
|
|
1523
|
+
return normalized;
|
|
1524
|
+
}
|
|
1525
|
+
get() {
|
|
1526
|
+
if (!this.config) {
|
|
1527
|
+
throw new GroqError(
|
|
1528
|
+
"MISSING_CONFIG" /* MISSING_CONFIG */,
|
|
1529
|
+
"Client not initialized. Call configureProvider first."
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
return this.config;
|
|
1533
|
+
}
|
|
1534
|
+
getOrNull() {
|
|
1535
|
+
return this.config;
|
|
1536
|
+
}
|
|
1537
|
+
isInitialized() {
|
|
1538
|
+
return this.config !== null;
|
|
1539
|
+
}
|
|
1540
|
+
setApiKey(apiKey) {
|
|
1541
|
+
const current = this.get();
|
|
1542
|
+
const validation = validateApiKey(apiKey, { minLength: 1 });
|
|
1543
|
+
if (!validation.valid) {
|
|
1544
|
+
throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
|
|
1545
|
+
}
|
|
1546
|
+
this.config = { ...current, apiKey: apiKey.trim() };
|
|
1547
|
+
}
|
|
1548
|
+
reset() {
|
|
1549
|
+
this.config = null;
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1386
1553
|
// src/providers/groq/groq-http-transport.ts
|
|
1387
1554
|
var GroqHttpTransport = class {
|
|
1388
|
-
constructor(
|
|
1389
|
-
this.
|
|
1555
|
+
constructor(configSource) {
|
|
1556
|
+
this.configSource = configSource;
|
|
1390
1557
|
}
|
|
1391
|
-
|
|
1558
|
+
configSource;
|
|
1392
1559
|
pendingAbortControllers = /* @__PURE__ */ new Set();
|
|
1560
|
+
getConfig() {
|
|
1561
|
+
if ("get" in this.configSource && typeof this.configSource.get === "function") {
|
|
1562
|
+
return this.configSource.get();
|
|
1563
|
+
}
|
|
1564
|
+
return this.configSource;
|
|
1565
|
+
}
|
|
1393
1566
|
cancelPendingRequests() {
|
|
1394
1567
|
for (const controller of this.pendingAbortControllers) {
|
|
1395
1568
|
controller.abort();
|
|
@@ -1404,7 +1577,7 @@ var GroqHttpTransport = class {
|
|
|
1404
1577
|
}
|
|
1405
1578
|
async streamChatCompletion(request, options = {}) {
|
|
1406
1579
|
const url = `${this.baseUrl()}${CHAT_COMPLETIONS_PATH}`;
|
|
1407
|
-
const timeout = this.
|
|
1580
|
+
const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
|
|
1408
1581
|
const controller = new AbortController();
|
|
1409
1582
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1410
1583
|
const onExternalAbort = () => controller.abort();
|
|
@@ -1419,7 +1592,7 @@ var GroqHttpTransport = class {
|
|
|
1419
1592
|
});
|
|
1420
1593
|
if (!response.ok) await this.throwFromErrorResponse(response);
|
|
1421
1594
|
if (!response.body) {
|
|
1422
|
-
throw new
|
|
1595
|
+
throw new AIError("NETWORK_ERROR" /* NETWORK_ERROR */, "Response body is null");
|
|
1423
1596
|
}
|
|
1424
1597
|
return response.body;
|
|
1425
1598
|
} catch (error) {
|
|
@@ -1431,11 +1604,11 @@ var GroqHttpTransport = class {
|
|
|
1431
1604
|
}
|
|
1432
1605
|
}
|
|
1433
1606
|
baseUrl() {
|
|
1434
|
-
return this.
|
|
1607
|
+
return this.getConfig().baseUrl ?? GROQ_BASE_URL;
|
|
1435
1608
|
}
|
|
1436
1609
|
async request(endpoint, body) {
|
|
1437
1610
|
const url = `${this.baseUrl()}${endpoint}`;
|
|
1438
|
-
const timeout = this.
|
|
1611
|
+
const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
|
|
1439
1612
|
const controller = new AbortController();
|
|
1440
1613
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1441
1614
|
this.pendingAbortControllers.add(controller);
|
|
@@ -1458,7 +1631,7 @@ var GroqHttpTransport = class {
|
|
|
1458
1631
|
buildHeaders() {
|
|
1459
1632
|
return {
|
|
1460
1633
|
[HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
|
|
1461
|
-
[HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.
|
|
1634
|
+
[HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.getConfig().apiKey}`
|
|
1462
1635
|
};
|
|
1463
1636
|
}
|
|
1464
1637
|
async throwFromErrorResponse(response) {
|
|
@@ -1467,7 +1640,7 @@ var GroqHttpTransport = class {
|
|
|
1467
1640
|
status: response.status,
|
|
1468
1641
|
url: response.url
|
|
1469
1642
|
};
|
|
1470
|
-
throw new
|
|
1643
|
+
throw new AIError(
|
|
1471
1644
|
classifyHttpStatus(response.status),
|
|
1472
1645
|
errorMessage,
|
|
1473
1646
|
void 0,
|
|
@@ -1475,24 +1648,24 @@ var GroqHttpTransport = class {
|
|
|
1475
1648
|
);
|
|
1476
1649
|
}
|
|
1477
1650
|
normalizeError(error) {
|
|
1478
|
-
if (error instanceof
|
|
1651
|
+
if (error instanceof AIError) return error;
|
|
1479
1652
|
if (error instanceof Error) {
|
|
1480
1653
|
if (error.name === "AbortError") {
|
|
1481
|
-
return new
|
|
1654
|
+
return new AIError(
|
|
1482
1655
|
"ABORT_ERROR" /* ABORT_ERROR */,
|
|
1483
1656
|
"Request was aborted by the client",
|
|
1484
1657
|
error
|
|
1485
1658
|
);
|
|
1486
1659
|
}
|
|
1487
1660
|
if (error.name === "TypeError" && error.message.toLowerCase().includes("network")) {
|
|
1488
|
-
return new
|
|
1661
|
+
return new AIError(
|
|
1489
1662
|
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1490
1663
|
"Network error: Unable to connect to the AI service",
|
|
1491
1664
|
error
|
|
1492
1665
|
);
|
|
1493
1666
|
}
|
|
1494
1667
|
}
|
|
1495
|
-
return new
|
|
1668
|
+
return new AIError(
|
|
1496
1669
|
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
1497
1670
|
error instanceof Error ? error.message : "Unknown error occurred",
|
|
1498
1671
|
error instanceof Error ? error : void 0
|
|
@@ -1560,21 +1733,40 @@ var CompositeExecutor = class {
|
|
|
1560
1733
|
|
|
1561
1734
|
// src/providers/groq/groq-http-client.ts
|
|
1562
1735
|
var GroqHttpClient = class {
|
|
1736
|
+
configStore;
|
|
1563
1737
|
transport;
|
|
1564
1738
|
executor;
|
|
1565
|
-
constructor(
|
|
1566
|
-
|
|
1739
|
+
constructor(configOrDeps, maybeDeps = {}) {
|
|
1740
|
+
const isConfig = typeof configOrDeps === "object" && configOrDeps !== null && "apiKey" in configOrDeps && typeof configOrDeps.apiKey === "string";
|
|
1741
|
+
const deps = isConfig ? maybeDeps : configOrDeps ?? {};
|
|
1742
|
+
this.configStore = deps.configStore ?? new GroqConfigStore();
|
|
1743
|
+
if (isConfig) {
|
|
1744
|
+
this.configStore.initialize(configOrDeps);
|
|
1745
|
+
}
|
|
1746
|
+
this.transport = deps.transport ?? new GroqHttpTransport(this.configStore);
|
|
1567
1747
|
const retryManager2 = deps.retryManager ?? new RetryManager();
|
|
1568
1748
|
const queue = deps.queue ?? new RequestQueue();
|
|
1569
1749
|
const deduplicator = deps.deduplicator ?? new RequestDeduplicator();
|
|
1570
1750
|
this.executor = new CompositeExecutor([
|
|
1571
1751
|
new DedupeExecutor(deduplicator, {
|
|
1572
|
-
generateKey: (request) => deduplicatorKey(request,
|
|
1752
|
+
generateKey: (request) => deduplicatorKey(request, this.configStore.isInitialized() ? this.configStore.get().apiKey : "")
|
|
1573
1753
|
}),
|
|
1574
1754
|
new RetryExecutor(retryManager2),
|
|
1575
1755
|
new QueueExecutor(queue, QUEUE_PRIORITY_USER_REQUEST)
|
|
1576
1756
|
]);
|
|
1577
1757
|
}
|
|
1758
|
+
initialize(config) {
|
|
1759
|
+
this.configStore.initialize(config);
|
|
1760
|
+
}
|
|
1761
|
+
isInitialized() {
|
|
1762
|
+
return this.configStore.isInitialized();
|
|
1763
|
+
}
|
|
1764
|
+
getApiKey() {
|
|
1765
|
+
return this.configStore.get().apiKey;
|
|
1766
|
+
}
|
|
1767
|
+
setApiKey(apiKey) {
|
|
1768
|
+
this.configStore.setApiKey(apiKey);
|
|
1769
|
+
}
|
|
1578
1770
|
cancelPendingRequests() {
|
|
1579
1771
|
this.transport.cancelPendingRequests();
|
|
1580
1772
|
}
|
|
@@ -1587,6 +1779,10 @@ var GroqHttpClient = class {
|
|
|
1587
1779
|
streamChatCompletion(request, options) {
|
|
1588
1780
|
return this.transport.streamChatCompletion(request, options);
|
|
1589
1781
|
}
|
|
1782
|
+
reset() {
|
|
1783
|
+
this.transport.cancelPendingRequests();
|
|
1784
|
+
this.configStore.reset();
|
|
1785
|
+
}
|
|
1590
1786
|
};
|
|
1591
1787
|
function deduplicatorKey(request, apiKey) {
|
|
1592
1788
|
const { model, messages, stream: _stream, ...rest } = request;
|
|
@@ -1634,6 +1830,13 @@ async function consumeGroqStream(stream, callbacks) {
|
|
|
1634
1830
|
} catch {
|
|
1635
1831
|
return;
|
|
1636
1832
|
}
|
|
1833
|
+
const errorBody = chunk.error;
|
|
1834
|
+
if (errorBody) {
|
|
1835
|
+
throw new AIError(
|
|
1836
|
+
errorBody.message?.toLowerCase().includes("rate limit") ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
|
|
1837
|
+
errorBody.message || "Groq stream failed mid-flight"
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1637
1840
|
if (chunk.model) model = chunk.model;
|
|
1638
1841
|
const choice = chunk.choices?.[0];
|
|
1639
1842
|
const delta = choice?.delta?.content;
|
|
@@ -1648,13 +1851,6 @@ async function consumeGroqStream(stream, callbacks) {
|
|
|
1648
1851
|
if (tailUsage) {
|
|
1649
1852
|
usage = mapGroqUsage(tailUsage);
|
|
1650
1853
|
}
|
|
1651
|
-
const errorBody = chunk.error;
|
|
1652
|
-
if (errorBody) {
|
|
1653
|
-
throw new AIError2(
|
|
1654
|
-
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
1655
|
-
errorBody.message || "Groq stream failed mid-flight"
|
|
1656
|
-
);
|
|
1657
|
-
}
|
|
1658
1854
|
};
|
|
1659
1855
|
try {
|
|
1660
1856
|
while (true) {
|
|
@@ -1668,8 +1864,8 @@ async function consumeGroqStream(stream, callbacks) {
|
|
|
1668
1864
|
buffer += decoder.decode();
|
|
1669
1865
|
if (buffer.length > 0) consumeLine(buffer);
|
|
1670
1866
|
} catch (error) {
|
|
1671
|
-
if (error instanceof
|
|
1672
|
-
throw new
|
|
1867
|
+
if (error instanceof AIError) throw error;
|
|
1868
|
+
throw new AIError(
|
|
1673
1869
|
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1674
1870
|
"Stream was interrupted",
|
|
1675
1871
|
error instanceof Error ? error : void 0
|
|
@@ -1727,7 +1923,7 @@ var GroqProvider = class {
|
|
|
1727
1923
|
const choice = response.choices?.[0];
|
|
1728
1924
|
const text = choice?.message?.content ?? "";
|
|
1729
1925
|
if (text.length === 0) {
|
|
1730
|
-
throw new
|
|
1926
|
+
throw new AIError(
|
|
1731
1927
|
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
1732
1928
|
"No content generated from Groq API",
|
|
1733
1929
|
void 0,
|
|
@@ -1749,7 +1945,7 @@ var GroqProvider = class {
|
|
|
1749
1945
|
const messages = toGroqMessages(request.messages, request.systemPrompt);
|
|
1750
1946
|
const lastUserIndex = findLastIndex2(messages, (m) => m.role === "user");
|
|
1751
1947
|
if (lastUserIndex === -1) {
|
|
1752
|
-
throw new
|
|
1948
|
+
throw new AIError(
|
|
1753
1949
|
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
1754
1950
|
"generateStructured requires at least one user message"
|
|
1755
1951
|
);
|
|
@@ -1763,7 +1959,7 @@ var GroqProvider = class {
|
|
|
1763
1959
|
const choice = response.choices?.[0];
|
|
1764
1960
|
const text = choice?.message?.content ?? "";
|
|
1765
1961
|
if (text.length === 0) {
|
|
1766
|
-
throw new
|
|
1962
|
+
throw new AIError(
|
|
1767
1963
|
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
1768
1964
|
"No content generated from Groq API",
|
|
1769
1965
|
void 0,
|
|
@@ -1790,7 +1986,7 @@ var GroqProvider = class {
|
|
|
1790
1986
|
try {
|
|
1791
1987
|
const outcome = await consumeGroqStream(streamBody, callbacks);
|
|
1792
1988
|
if (outcome.text.length === 0) {
|
|
1793
|
-
throw new
|
|
1989
|
+
throw new AIError(
|
|
1794
1990
|
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
1795
1991
|
"No content generated from Groq API",
|
|
1796
1992
|
void 0,
|
|
@@ -1974,7 +2170,9 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
1974
2170
|
headers: this.authHeaders()
|
|
1975
2171
|
}
|
|
1976
2172
|
);
|
|
1977
|
-
|
|
2173
|
+
if (response.ok) return "healthy";
|
|
2174
|
+
if (response.status === 401 || response.status === 403) return "unavailable";
|
|
2175
|
+
return "degraded";
|
|
1978
2176
|
} catch {
|
|
1979
2177
|
return "unavailable";
|
|
1980
2178
|
}
|
|
@@ -1985,6 +2183,8 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
1985
2183
|
async generate(request) {
|
|
1986
2184
|
try {
|
|
1987
2185
|
const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
|
|
2186
|
+
const systemMessages = request.messages.filter((m) => m.role === "system");
|
|
2187
|
+
const systemPrompt = request.systemPrompt && request.systemPrompt.length > 0 ? request.systemPrompt : systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n") : void 0;
|
|
1988
2188
|
const response = await this.withRetry(async () => {
|
|
1989
2189
|
return await this.fetchWithTimeout(
|
|
1990
2190
|
`${this.config.baseUrl}/models/${model}:generateContent`,
|
|
@@ -1992,24 +2192,30 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
1992
2192
|
method: "POST",
|
|
1993
2193
|
headers: this.authHeaders(),
|
|
1994
2194
|
body: JSON.stringify({
|
|
1995
|
-
systemInstruction:
|
|
2195
|
+
systemInstruction: systemPrompt && systemPrompt.length > 0 ? { parts: [{ text: systemPrompt }] } : void 0,
|
|
1996
2196
|
contents: toGeminiContents(request.messages),
|
|
1997
2197
|
generationConfig: {
|
|
1998
|
-
temperature: request.temperature
|
|
1999
|
-
maxOutputTokens: request.maxTokens
|
|
2000
|
-
topP: request.topP
|
|
2198
|
+
temperature: request.temperature ?? 0.7,
|
|
2199
|
+
maxOutputTokens: request.maxTokens ?? 2048,
|
|
2200
|
+
topP: request.topP ?? 0.9
|
|
2001
2201
|
}
|
|
2002
2202
|
})
|
|
2003
2203
|
}
|
|
2004
2204
|
);
|
|
2005
2205
|
});
|
|
2006
2206
|
if (!response.ok) {
|
|
2007
|
-
|
|
2207
|
+
const message = await extractErrorMessageFromResponse(response);
|
|
2208
|
+
throw new AIError(
|
|
2209
|
+
classifyHttpStatus(response.status),
|
|
2210
|
+
`Gemini API error (${response.status}): ${message}`,
|
|
2211
|
+
void 0,
|
|
2212
|
+
{ status: response.status, model }
|
|
2213
|
+
);
|
|
2008
2214
|
}
|
|
2009
2215
|
const data = await response.json();
|
|
2010
|
-
const text = data.candidates[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
|
|
2216
|
+
const text = data.candidates?.[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
|
|
2011
2217
|
if (text.length === 0) {
|
|
2012
|
-
throw new
|
|
2218
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No content generated from Gemini API", void 0, { model });
|
|
2013
2219
|
}
|
|
2014
2220
|
return {
|
|
2015
2221
|
text,
|
|
@@ -2020,11 +2226,11 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
2020
2226
|
} : void 0,
|
|
2021
2227
|
model,
|
|
2022
2228
|
providerId: this.id,
|
|
2023
|
-
finishReason: mapGeminiFinishReason(data.candidates[0]?.finishReason)
|
|
2229
|
+
finishReason: mapGeminiFinishReason(data.candidates?.[0]?.finishReason)
|
|
2024
2230
|
};
|
|
2025
2231
|
} catch (error) {
|
|
2026
|
-
if (error instanceof
|
|
2027
|
-
throw new
|
|
2232
|
+
if (error instanceof AIError) throw error;
|
|
2233
|
+
throw new AIError(
|
|
2028
2234
|
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
2029
2235
|
`Gemini text generation failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
2030
2236
|
error instanceof Error ? error : void 0
|
|
@@ -2035,7 +2241,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
2035
2241
|
const messages = [...request.messages];
|
|
2036
2242
|
const lastUserIndex = findLastIndex3(messages, (m) => m.role === "user");
|
|
2037
2243
|
if (lastUserIndex === -1) {
|
|
2038
|
-
throw new
|
|
2244
|
+
throw new AIError(
|
|
2039
2245
|
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
2040
2246
|
"generateStructured requires at least one user message"
|
|
2041
2247
|
);
|
|
@@ -2072,12 +2278,18 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
2072
2278
|
);
|
|
2073
2279
|
});
|
|
2074
2280
|
if (!response.ok) {
|
|
2075
|
-
|
|
2281
|
+
const message = await extractErrorMessageFromResponse(response);
|
|
2282
|
+
throw new AIError(
|
|
2283
|
+
classifyHttpStatus(response.status),
|
|
2284
|
+
`Gemini image generation error (${response.status}): ${message}`,
|
|
2285
|
+
void 0,
|
|
2286
|
+
{ status: response.status }
|
|
2287
|
+
);
|
|
2076
2288
|
}
|
|
2077
2289
|
const data = await response.json();
|
|
2078
|
-
const imageData = data.candidates[0]?.content?.parts[0]?.inlineData;
|
|
2290
|
+
const imageData = data.candidates?.[0]?.content?.parts?.[0]?.inlineData;
|
|
2079
2291
|
if (!imageData) {
|
|
2080
|
-
throw new
|
|
2292
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image data in Gemini response");
|
|
2081
2293
|
}
|
|
2082
2294
|
const url = `data:${imageData.mimeType};base64,${imageData.data}`;
|
|
2083
2295
|
return {
|
|
@@ -2456,6 +2668,41 @@ var FalProvider = class extends BaseProvider {
|
|
|
2456
2668
|
}
|
|
2457
2669
|
};
|
|
2458
2670
|
|
|
2671
|
+
// src/providers/pruna/pruna-generate.ts
|
|
2672
|
+
var prunaCorePromise = null;
|
|
2673
|
+
async function loadPrunaCore() {
|
|
2674
|
+
if (!prunaCorePromise) {
|
|
2675
|
+
const specifier = "@umituz/pruna-provider/core";
|
|
2676
|
+
prunaCorePromise = import(specifier);
|
|
2677
|
+
}
|
|
2678
|
+
return prunaCorePromise;
|
|
2679
|
+
}
|
|
2680
|
+
async function* generateWithPruna(request, apiKey, signal, onProgress) {
|
|
2681
|
+
const prunaCore = await loadPrunaCore();
|
|
2682
|
+
if (prunaCore.generateWithPruna) {
|
|
2683
|
+
yield* prunaCore.generateWithPruna(request, apiKey, signal, onProgress);
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
if (!prunaCore.submitPrediction || !prunaCore.pollForResult) {
|
|
2687
|
+
throw new Error("@umituz/pruna-provider/core does not export required functions");
|
|
2688
|
+
}
|
|
2689
|
+
const modelId = request.model || (request.image ? "p-image-edit" : "p-image");
|
|
2690
|
+
const prediction = await prunaCore.submitPrediction(modelId, request, apiKey, signal, onProgress);
|
|
2691
|
+
if (prediction.output && typeof prediction.output === "object" && "url" in prediction.output) {
|
|
2692
|
+
yield {
|
|
2693
|
+
stage: "predicting",
|
|
2694
|
+
result: { url: prediction.output.url, model: modelId }
|
|
2695
|
+
};
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
const pollUrl = prediction.status_url || prediction.generation_url;
|
|
2699
|
+
if (!pollUrl) {
|
|
2700
|
+
throw new Error("Pruna prediction response missing polling URL");
|
|
2701
|
+
}
|
|
2702
|
+
const resultUrl = await prunaCore.pollForResult(pollUrl, apiKey, 60, 2e3, signal, onProgress);
|
|
2703
|
+
yield { stage: "polling", result: { url: resultUrl, model: modelId } };
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2459
2706
|
// src/providers/pruna/pruna.provider.ts
|
|
2460
2707
|
var PrunaProvider = class {
|
|
2461
2708
|
constructor(config) {
|
|
@@ -2471,17 +2718,70 @@ var PrunaProvider = class {
|
|
|
2471
2718
|
async estimateCost(_request) {
|
|
2472
2719
|
return 1;
|
|
2473
2720
|
}
|
|
2474
|
-
async generateImage(
|
|
2475
|
-
|
|
2721
|
+
async generateImage(request) {
|
|
2722
|
+
let finalUrl;
|
|
2723
|
+
const model = this.config.models?.textToImage;
|
|
2724
|
+
for await (const stage of generateWithPruna(
|
|
2725
|
+
{ prompt: request.prompt, model, quality: request.quality },
|
|
2726
|
+
this.config.apiKey
|
|
2727
|
+
)) {
|
|
2728
|
+
if (stage.error) throw stage.error;
|
|
2729
|
+
if (stage.result?.url) finalUrl = stage.result.url;
|
|
2730
|
+
}
|
|
2731
|
+
if (!finalUrl) {
|
|
2732
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image generated by Pruna AI");
|
|
2733
|
+
}
|
|
2734
|
+
return {
|
|
2735
|
+
id: Date.now().toString(),
|
|
2736
|
+
type: "image",
|
|
2737
|
+
url: finalUrl,
|
|
2738
|
+
metadata: { provider: this.id, model: model ?? "p-image" },
|
|
2739
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2740
|
+
};
|
|
2476
2741
|
}
|
|
2477
|
-
async generateVideo(
|
|
2478
|
-
|
|
2742
|
+
async generateVideo(request) {
|
|
2743
|
+
let finalUrl;
|
|
2744
|
+
const model = this.config.models?.textToVideo;
|
|
2745
|
+
for await (const stage of generateWithPruna(
|
|
2746
|
+
{ prompt: request.prompt, model, duration: request.duration, aspectRatio: request.aspectRatio },
|
|
2747
|
+
this.config.apiKey
|
|
2748
|
+
)) {
|
|
2749
|
+
if (stage.error) throw stage.error;
|
|
2750
|
+
if (stage.result?.url) finalUrl = stage.result.url;
|
|
2751
|
+
}
|
|
2752
|
+
if (!finalUrl) {
|
|
2753
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
|
|
2754
|
+
}
|
|
2755
|
+
return {
|
|
2756
|
+
id: Date.now().toString(),
|
|
2757
|
+
type: "video",
|
|
2758
|
+
url: finalUrl,
|
|
2759
|
+
metadata: { provider: this.id, model: model ?? "p-video" },
|
|
2760
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2761
|
+
};
|
|
2479
2762
|
}
|
|
2480
|
-
async imageToVideo(
|
|
2481
|
-
|
|
2763
|
+
async imageToVideo(request) {
|
|
2764
|
+
let finalUrl;
|
|
2765
|
+
for await (const stage of generateWithPruna(
|
|
2766
|
+
{ image: request.imageUrl, prompt: request.prompt, duration: request.duration, motion: request.motion },
|
|
2767
|
+
this.config.apiKey
|
|
2768
|
+
)) {
|
|
2769
|
+
if (stage.error) throw stage.error;
|
|
2770
|
+
if (stage.result?.url) finalUrl = stage.result.url;
|
|
2771
|
+
}
|
|
2772
|
+
if (!finalUrl) {
|
|
2773
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated from image by Pruna AI");
|
|
2774
|
+
}
|
|
2775
|
+
return {
|
|
2776
|
+
id: Date.now().toString(),
|
|
2777
|
+
type: "video",
|
|
2778
|
+
url: finalUrl,
|
|
2779
|
+
metadata: { provider: this.id, model: "p-image-to-video" },
|
|
2780
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2781
|
+
};
|
|
2482
2782
|
}
|
|
2483
2783
|
async videoToVideo(_request) {
|
|
2484
|
-
throw new
|
|
2784
|
+
throw new AIError("CLIENT_ERROR" /* CLIENT_ERROR */, "Pruna AI provider does not support video-to-video conversion");
|
|
2485
2785
|
}
|
|
2486
2786
|
};
|
|
2487
2787
|
|
|
@@ -3088,11 +3388,12 @@ var buildScriptResult = (parsed, duration) => {
|
|
|
3088
3388
|
if (!script) {
|
|
3089
3389
|
throw new Error("AI response missing script field");
|
|
3090
3390
|
}
|
|
3391
|
+
const wordCount = script.trim().length === 0 ? 0 : script.trim().split(/\s+/).length;
|
|
3091
3392
|
return {
|
|
3092
3393
|
...parsed,
|
|
3093
3394
|
script,
|
|
3094
3395
|
duration,
|
|
3095
|
-
wordCount
|
|
3396
|
+
wordCount
|
|
3096
3397
|
};
|
|
3097
3398
|
};
|
|
3098
3399
|
var VideoScriptService = class {
|
|
@@ -3287,7 +3588,6 @@ var SeoService = class {
|
|
|
3287
3588
|
analyzeKeywords(content, keywords) {
|
|
3288
3589
|
const validatedContent = validateContentBody(content);
|
|
3289
3590
|
const validatedKeywords = validateKeywordList(keywords);
|
|
3290
|
-
void countWordsInContent(validatedContent);
|
|
3291
3591
|
return validatedKeywords.map((keyword) => {
|
|
3292
3592
|
const count = countKeywordOccurrences(validatedContent, keyword);
|
|
3293
3593
|
const density = calculateKeywordDensity(validatedContent, keyword);
|
|
@@ -3382,68 +3682,20 @@ var ABTestService = class {
|
|
|
3382
3682
|
});
|
|
3383
3683
|
const scoreA = predictions[0]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
|
|
3384
3684
|
const scoreB = predictions[1]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
|
|
3685
|
+
const isTie = scoreA === scoreB;
|
|
3385
3686
|
const winner = selectHigherScoreVariantId(scoreA, scoreB, VARIANT_A_ID, VARIANT_B_ID);
|
|
3386
3687
|
const improvement = calculateImprovementPercentage(scoreA, scoreB);
|
|
3387
3688
|
return {
|
|
3388
3689
|
winner,
|
|
3389
3690
|
confidence: AB_TEST_DEFAULT_CONFIDENCE,
|
|
3390
|
-
improvement: `+${improvement}% engagement`,
|
|
3391
|
-
reasoning: winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
|
|
3691
|
+
improvement: isTie ? "0%" : `+${improvement}% engagement`,
|
|
3692
|
+
reasoning: isTie ? "Both variants have equal predicted engagement" : winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
|
|
3392
3693
|
recommendations: [...DEFAULT_RECOMMENDATIONS]
|
|
3393
3694
|
};
|
|
3394
3695
|
}
|
|
3395
3696
|
};
|
|
3396
3697
|
|
|
3397
|
-
// src/content/infrastructure/utils/jsonParser.ts
|
|
3398
|
-
function repairCommonJsonDefects(text) {
|
|
3399
|
-
return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
|
|
3400
|
-
}
|
|
3401
|
-
function safeJSONParse(text, fallback, onError) {
|
|
3402
|
-
try {
|
|
3403
|
-
return JSON.parse(text);
|
|
3404
|
-
} catch (error) {
|
|
3405
|
-
try {
|
|
3406
|
-
return JSON.parse(repairCommonJsonDefects(text));
|
|
3407
|
-
} catch {
|
|
3408
|
-
}
|
|
3409
|
-
if (error instanceof SyntaxError) {
|
|
3410
|
-
const positionMatch = error.message.match(/position (\d+)/);
|
|
3411
|
-
const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
|
|
3412
|
-
if (position > 0) {
|
|
3413
|
-
text.substring(Math.max(0, position - 50), position + 50);
|
|
3414
|
-
}
|
|
3415
|
-
}
|
|
3416
|
-
return fallback;
|
|
3417
|
-
}
|
|
3418
|
-
}
|
|
3419
|
-
function extractJSON(text) {
|
|
3420
|
-
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
3421
|
-
if (codeBlockMatch && codeBlockMatch[1]) {
|
|
3422
|
-
const candidate = codeBlockMatch[1].trim();
|
|
3423
|
-
if (candidate.startsWith("{") || candidate.startsWith("[")) {
|
|
3424
|
-
return candidate;
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
const objectMatch = text.match(/\{[\s\S]*\}/);
|
|
3428
|
-
if (objectMatch) {
|
|
3429
|
-
return objectMatch[0];
|
|
3430
|
-
}
|
|
3431
|
-
const arrayMatch = text.match(/\[[\s\S]*\]/);
|
|
3432
|
-
if (arrayMatch) {
|
|
3433
|
-
return arrayMatch[0];
|
|
3434
|
-
}
|
|
3435
|
-
return null;
|
|
3436
|
-
}
|
|
3437
|
-
function parseAIResponse(response, fallback, onError) {
|
|
3438
|
-
const json = extractJSON(response);
|
|
3439
|
-
if (!json) {
|
|
3440
|
-
return fallback;
|
|
3441
|
-
}
|
|
3442
|
-
return safeJSONParse(json, fallback);
|
|
3443
|
-
}
|
|
3444
|
-
|
|
3445
3698
|
// src/content/application/services/ImagePromptService.ts
|
|
3446
|
-
var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
|
|
3447
3699
|
var ImagePromptService = class {
|
|
3448
3700
|
constructor(executor) {
|
|
3449
3701
|
this.executor = executor;
|
|
@@ -3457,10 +3709,11 @@ var ImagePromptService = class {
|
|
|
3457
3709
|
async generateImagePromptsForBlog(blogContent) {
|
|
3458
3710
|
const truncated = truncateForBlogPromptPreview(validateContentBody(blogContent));
|
|
3459
3711
|
const prompt = PromptBuilder.blogImagePrompts(truncated);
|
|
3460
|
-
const
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3712
|
+
const prompts = await this.executor.runJson(prompt, [], {
|
|
3713
|
+
maxTokens: 500,
|
|
3714
|
+
temperature: 0.8
|
|
3715
|
+
});
|
|
3716
|
+
return Array.isArray(prompts) ? prompts : [];
|
|
3464
3717
|
}
|
|
3465
3718
|
};
|
|
3466
3719
|
|
|
@@ -3504,11 +3757,16 @@ var ContentCalendarService = class {
|
|
|
3504
3757
|
const validatedNiche = validateTopicText(niche);
|
|
3505
3758
|
const validatedDays = validateCalendarDays(days);
|
|
3506
3759
|
const prompt = PromptBuilder.contentCalendar(validatedNiche, validatedDays);
|
|
3507
|
-
const data = await this.executor.runJson(
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3760
|
+
const data = await this.executor.runJson(
|
|
3761
|
+
prompt,
|
|
3762
|
+
[],
|
|
3763
|
+
{
|
|
3764
|
+
maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
|
|
3765
|
+
temperature: 0.8
|
|
3766
|
+
}
|
|
3767
|
+
);
|
|
3768
|
+
const items = Array.isArray(data) ? data : Array.isArray(data?.calendar) ? data.calendar : Array.isArray(data?.entries) ? data.entries : [];
|
|
3769
|
+
return items.map(toCalendarEntry);
|
|
3512
3770
|
}
|
|
3513
3771
|
};
|
|
3514
3772
|
|
|
@@ -3556,6 +3814,54 @@ var MediaGenerationService = class {
|
|
|
3556
3814
|
}
|
|
3557
3815
|
};
|
|
3558
3816
|
|
|
3817
|
+
// src/content/infrastructure/utils/jsonParser.ts
|
|
3818
|
+
function repairCommonJsonDefects2(text) {
|
|
3819
|
+
return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
|
|
3820
|
+
}
|
|
3821
|
+
function safeJSONParse(text, fallback, onError) {
|
|
3822
|
+
try {
|
|
3823
|
+
return JSON.parse(text);
|
|
3824
|
+
} catch (error) {
|
|
3825
|
+
try {
|
|
3826
|
+
return JSON.parse(repairCommonJsonDefects2(text));
|
|
3827
|
+
} catch {
|
|
3828
|
+
}
|
|
3829
|
+
if (error instanceof SyntaxError) {
|
|
3830
|
+
const positionMatch = error.message.match(/position (\d+)/);
|
|
3831
|
+
const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
|
|
3832
|
+
if (position > 0) {
|
|
3833
|
+
text.substring(Math.max(0, position - 50), position + 50);
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
return fallback;
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
function extractJSON(text) {
|
|
3840
|
+
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
3841
|
+
if (codeBlockMatch && codeBlockMatch[1]) {
|
|
3842
|
+
const candidate = codeBlockMatch[1].trim();
|
|
3843
|
+
if (candidate.startsWith("{") || candidate.startsWith("[")) {
|
|
3844
|
+
return candidate;
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
const objectMatch = text.match(/\{[\s\S]*\}/);
|
|
3848
|
+
if (objectMatch) {
|
|
3849
|
+
return objectMatch[0];
|
|
3850
|
+
}
|
|
3851
|
+
const arrayMatch = text.match(/\[[\s\S]*\]/);
|
|
3852
|
+
if (arrayMatch) {
|
|
3853
|
+
return arrayMatch[0];
|
|
3854
|
+
}
|
|
3855
|
+
return null;
|
|
3856
|
+
}
|
|
3857
|
+
function parseAIResponse(response, fallback, onError) {
|
|
3858
|
+
const json = extractJSON(response);
|
|
3859
|
+
if (!json) {
|
|
3860
|
+
return fallback;
|
|
3861
|
+
}
|
|
3862
|
+
return safeJSONParse(json, fallback);
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3559
3865
|
// src/content/application/services/GenerationExecutor.ts
|
|
3560
3866
|
var GenerationExecutor = class {
|
|
3561
3867
|
constructor(generator) {
|
|
@@ -3586,6 +3892,13 @@ var isTextGenerator = (value) => typeof value === "object" && value !== null &&
|
|
|
3586
3892
|
function toAIProviderConfig(config) {
|
|
3587
3893
|
const legacy = config;
|
|
3588
3894
|
const modern = config;
|
|
3895
|
+
const anthropic = modern.anthropic ?? (legacy.anthropic?.enabled ? {
|
|
3896
|
+
apiKey: legacy.anthropic.apiKey,
|
|
3897
|
+
baseUrl: legacy.anthropic.baseUrl,
|
|
3898
|
+
timeoutMs: legacy.anthropic.timeout,
|
|
3899
|
+
models: legacy.anthropic.models,
|
|
3900
|
+
effort: legacy.anthropic.effort
|
|
3901
|
+
} : void 0);
|
|
3589
3902
|
const groq = modern.groq ?? (legacy.groq?.enabled ? {
|
|
3590
3903
|
apiKey: legacy.groq.apiKey,
|
|
3591
3904
|
baseUrl: legacy.groq.baseUrl,
|
|
@@ -3600,7 +3913,7 @@ function toAIProviderConfig(config) {
|
|
|
3600
3913
|
priority: modern.priority,
|
|
3601
3914
|
fallbackEnabled: modern.fallbackEnabled,
|
|
3602
3915
|
defaultTier: modern.defaultTier,
|
|
3603
|
-
anthropic:
|
|
3916
|
+
...anthropic ? { anthropic } : {},
|
|
3604
3917
|
...groq ? { groq } : {},
|
|
3605
3918
|
...gemini ? { gemini } : {},
|
|
3606
3919
|
...fal ? { fal } : {},
|