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