@umituz/web-ai 1.1.0 → 1.2.0
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/README.md +29 -3
- package/dist/{cancellation-error-_39nYWwz.d.cts → cancellation-error-BWb59Wkj.d.cts} +1 -1
- package/dist/{cancellation-error-hYcE9AS9.d.ts → cancellation-error-D9LETLCD.d.ts} +1 -1
- package/dist/chat/index.cjs +1 -0
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.js +1 -0
- package/dist/chat/index.js.map +1 -1
- package/dist/content/index.cjs +622 -8
- package/dist/content/index.cjs.map +1 -1
- package/dist/content/index.d.cts +2 -2
- package/dist/content/index.d.ts +2 -2
- package/dist/content/index.js +622 -8
- package/dist/content/index.js.map +1 -1
- package/dist/context-manager-CuZz0vnC.d.cts +104 -0
- package/dist/context-manager-CuZz0vnC.d.ts +104 -0
- package/dist/core/index.cjs +147 -10
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +91 -73
- package/dist/core/index.d.ts +91 -73
- package/dist/core/index.js +141 -11
- package/dist/core/index.js.map +1 -1
- package/dist/{create-ai-D-oEWzf9.d.ts → create-ai-Cq3HkJxd.d.ts} +61 -1
- package/dist/{create-ai-DRysRImc.d.cts → create-ai-DDyTVzef.d.cts} +61 -1
- package/dist/hooks/index.cjs +747 -15
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +57 -4
- package/dist/hooks/index.d.ts +57 -4
- package/dist/hooks/index.js +746 -16
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +1180 -242
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +1140 -243
- package/dist/index.js.map +1 -1
- package/dist/providers/index.cjs +1212 -362
- package/dist/providers/index.cjs.map +1 -1
- package/dist/providers/index.d.cts +422 -10
- package/dist/providers/index.d.ts +422 -10
- package/dist/providers/index.js +1179 -363
- package/dist/providers/index.js.map +1 -1
- package/dist/remote-chat-client-LrNlew4n.d.cts +70 -0
- package/dist/remote-chat-client-LrNlew4n.d.ts +70 -0
- package/dist/{requestQueue-D89wx91Q.d.cts → requestQueue--h2uZ-Q3.d.cts} +1 -1
- package/dist/{requestQueue-AP6n_bRH.d.ts → requestQueue-BGeCy4Fd.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/tool-call-accumulator-C7KhaXZA.d.cts +0 -69
- package/dist/tool-call-accumulator-C7KhaXZA.d.ts +0 -69
package/dist/content/index.cjs
CHANGED
|
@@ -698,6 +698,68 @@ async function extractErrorMessageFromResponse(response) {
|
|
|
698
698
|
return buildHttpStatusFallback(response.status, response.statusText);
|
|
699
699
|
}
|
|
700
700
|
|
|
701
|
+
// src/core/errors/unified-ai-error.ts
|
|
702
|
+
var STATUS_TO_CODE = {
|
|
703
|
+
400: "invalid_request",
|
|
704
|
+
401: "invalid_key",
|
|
705
|
+
403: "invalid_key",
|
|
706
|
+
404: "model_unavailable",
|
|
707
|
+
413: "invalid_request",
|
|
708
|
+
422: "invalid_request",
|
|
709
|
+
429: "rate_limited",
|
|
710
|
+
500: "upstream_error",
|
|
711
|
+
502: "upstream_error",
|
|
712
|
+
503: "capacity",
|
|
713
|
+
504: "timeout"
|
|
714
|
+
};
|
|
715
|
+
var CODE_MESSAGES = {
|
|
716
|
+
invalid_key: "The provider rejected the configured API key.",
|
|
717
|
+
missing_key: "The provider is not configured with an API key.",
|
|
718
|
+
rate_limited: "The provider rate limit was reached. Try again shortly.",
|
|
719
|
+
capacity: "The provider is temporarily at capacity. Try again shortly.",
|
|
720
|
+
timeout: "The model took too long to respond.",
|
|
721
|
+
invalid_request: "The request was rejected by the provider.",
|
|
722
|
+
model_unavailable: "The requested model is not available.",
|
|
723
|
+
upstream_error: "The provider returned an unexpected error.",
|
|
724
|
+
network: "Could not reach the provider.",
|
|
725
|
+
cancelled: "Generation was cancelled."
|
|
726
|
+
};
|
|
727
|
+
var AiError = class extends Error {
|
|
728
|
+
code;
|
|
729
|
+
constructor(code, message) {
|
|
730
|
+
super(message ?? CODE_MESSAGES[code]);
|
|
731
|
+
this.name = "AiError";
|
|
732
|
+
this.code = code;
|
|
733
|
+
}
|
|
734
|
+
get userMessage() {
|
|
735
|
+
return this.message;
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
function isApiKeyRejection(provider) {
|
|
739
|
+
if (provider.code === "invalid_api_key") return true;
|
|
740
|
+
return provider.type === void 0;
|
|
741
|
+
}
|
|
742
|
+
function errorFromStatus(status, detail, provider) {
|
|
743
|
+
let code = STATUS_TO_CODE[status] ?? "upstream_error";
|
|
744
|
+
if (status === 403 && provider && !isApiKeyRejection(provider)) {
|
|
745
|
+
code = "model_unavailable";
|
|
746
|
+
}
|
|
747
|
+
return new AiError(code, detail ? `${CODE_MESSAGES[code]} (${detail})` : void 0);
|
|
748
|
+
}
|
|
749
|
+
function errorFromUnknown(err) {
|
|
750
|
+
if (err instanceof AiError) return err;
|
|
751
|
+
if (err instanceof Error) {
|
|
752
|
+
if (err.name === "TimeoutError" || err.name === "AbortError") {
|
|
753
|
+
return new AiError("timeout");
|
|
754
|
+
}
|
|
755
|
+
if (err.name === "TypeError") {
|
|
756
|
+
return new AiError("network");
|
|
757
|
+
}
|
|
758
|
+
return new AiError("upstream_error", err.message);
|
|
759
|
+
}
|
|
760
|
+
return new AiError("upstream_error");
|
|
761
|
+
}
|
|
762
|
+
|
|
701
763
|
// src/providers/base/provider.types.ts
|
|
702
764
|
function isTextProvider(provider) {
|
|
703
765
|
return provider.capabilities.includes("text");
|
|
@@ -1452,7 +1514,7 @@ var AnthropicProvider = class {
|
|
|
1452
1514
|
);
|
|
1453
1515
|
}
|
|
1454
1516
|
const structuredPrompt = new StructuredPromptBuilder(
|
|
1455
|
-
messages[lastUserIndex]
|
|
1517
|
+
messages[lastUserIndex]?.content ?? ""
|
|
1456
1518
|
).withSchema(request.schema).build();
|
|
1457
1519
|
messages[lastUserIndex] = { role: "user", content: structuredPrompt };
|
|
1458
1520
|
const wireRequest = new AnthropicRequestBuilder(model).withRequest({ ...request, messages }).withEffort(this.config.effort).build();
|
|
@@ -1532,7 +1594,8 @@ function mapAnthropicUsage(usage) {
|
|
|
1532
1594
|
}
|
|
1533
1595
|
function findLastIndex(items, predicate) {
|
|
1534
1596
|
for (let i = items.length - 1; i >= 0; i--) {
|
|
1535
|
-
|
|
1597
|
+
const item = items[i];
|
|
1598
|
+
if (item !== void 0 && predicate(item)) return i;
|
|
1536
1599
|
}
|
|
1537
1600
|
return -1;
|
|
1538
1601
|
}
|
|
@@ -2377,7 +2440,7 @@ var GroqProvider = class {
|
|
|
2377
2440
|
);
|
|
2378
2441
|
}
|
|
2379
2442
|
const structuredPrompt = new StructuredPromptBuilder(
|
|
2380
|
-
messages[lastUserIndex]
|
|
2443
|
+
messages[lastUserIndex]?.content ?? ""
|
|
2381
2444
|
).withSchema(request.schema).build();
|
|
2382
2445
|
messages[lastUserIndex] = { role: "user", content: structuredPrompt };
|
|
2383
2446
|
const wireRequest = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(this.generationConfig(request, STRUCTURED_TEMPERATURE)).withJsonResponse(true).build();
|
|
@@ -2471,7 +2534,8 @@ function toGroqMessages(messages, systemPrompt) {
|
|
|
2471
2534
|
}
|
|
2472
2535
|
function findLastIndex2(items, predicate) {
|
|
2473
2536
|
for (let i = items.length - 1; i >= 0; i--) {
|
|
2474
|
-
|
|
2537
|
+
const item = items[i];
|
|
2538
|
+
if (item !== void 0 && predicate(item)) return i;
|
|
2475
2539
|
}
|
|
2476
2540
|
return -1;
|
|
2477
2541
|
}
|
|
@@ -2649,7 +2713,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
2649
2713
|
}
|
|
2650
2714
|
messages[lastUserIndex] = {
|
|
2651
2715
|
role: "user",
|
|
2652
|
-
content: new StructuredPromptBuilder(messages[lastUserIndex]
|
|
2716
|
+
content: new StructuredPromptBuilder(messages[lastUserIndex]?.content ?? "").withSchema(request.schema).build()
|
|
2653
2717
|
};
|
|
2654
2718
|
const result = await this.generate({ ...request, messages, temperature: 0.1 });
|
|
2655
2719
|
return { value: this.jsonParser.parse(result.text), result };
|
|
@@ -2755,7 +2819,8 @@ function mapGeminiFinishReason(reason) {
|
|
|
2755
2819
|
}
|
|
2756
2820
|
function findLastIndex3(items, predicate) {
|
|
2757
2821
|
for (let i = items.length - 1; i >= 0; i--) {
|
|
2758
|
-
|
|
2822
|
+
const item = items[i];
|
|
2823
|
+
if (item !== void 0 && predicate(item)) return i;
|
|
2759
2824
|
}
|
|
2760
2825
|
return -1;
|
|
2761
2826
|
}
|
|
@@ -3817,6 +3882,546 @@ var PrunaProvider = class {
|
|
|
3817
3882
|
}
|
|
3818
3883
|
};
|
|
3819
3884
|
|
|
3885
|
+
// src/core/sse/tool-call-accumulator.ts
|
|
3886
|
+
var ToolCallAccumulator = class {
|
|
3887
|
+
slots = /* @__PURE__ */ new Map();
|
|
3888
|
+
push(delta) {
|
|
3889
|
+
const index = delta.index ?? 0;
|
|
3890
|
+
const slot = this.slots.get(index) ?? { id: "", name: "", args: "" };
|
|
3891
|
+
if (delta.id) slot.id = delta.id;
|
|
3892
|
+
if (delta.function?.name) slot.name = delta.function.name;
|
|
3893
|
+
if (delta.function?.arguments) slot.args += delta.function.arguments;
|
|
3894
|
+
this.slots.set(index, slot);
|
|
3895
|
+
}
|
|
3896
|
+
finish() {
|
|
3897
|
+
return Array.from(this.slots.entries()).sort(([a], [b]) => a - b).map(([, slot]) => ({
|
|
3898
|
+
id: slot.id,
|
|
3899
|
+
name: slot.name,
|
|
3900
|
+
arguments: slot.args
|
|
3901
|
+
})).filter((call) => call.id.length > 0 || call.name.length > 0);
|
|
3902
|
+
}
|
|
3903
|
+
};
|
|
3904
|
+
|
|
3905
|
+
// src/providers/adapters/openai-compatible.adapter.ts
|
|
3906
|
+
function toWireMessage(message) {
|
|
3907
|
+
if (message.role === "tool") {
|
|
3908
|
+
return { role: "tool", content: message.content, tool_call_id: message.toolCallId ?? "" };
|
|
3909
|
+
}
|
|
3910
|
+
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
3911
|
+
return {
|
|
3912
|
+
role: "assistant",
|
|
3913
|
+
content: message.content,
|
|
3914
|
+
tool_calls: message.toolCalls.map((call) => ({
|
|
3915
|
+
id: call.id,
|
|
3916
|
+
type: "function",
|
|
3917
|
+
function: { name: call.name, arguments: call.arguments }
|
|
3918
|
+
}))
|
|
3919
|
+
};
|
|
3920
|
+
}
|
|
3921
|
+
if (message.role === "user") return { role: "user", content: message.content };
|
|
3922
|
+
return { role: message.role, content: message.content };
|
|
3923
|
+
}
|
|
3924
|
+
function mergeImages(messages, images) {
|
|
3925
|
+
if (images.length === 0) return [...messages];
|
|
3926
|
+
const parts = images.map((image) => ({
|
|
3927
|
+
type: "image_url",
|
|
3928
|
+
image_url: { url: `data:${image.mimeType};base64,${image.dataBase64}` }
|
|
3929
|
+
}));
|
|
3930
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3931
|
+
const message = messages[index];
|
|
3932
|
+
if (!message || message.role !== "user") continue;
|
|
3933
|
+
const textParts = typeof message.content === "string" ? message.content.length > 0 ? [{ type: "text", text: message.content }] : [] : message.content;
|
|
3934
|
+
return [
|
|
3935
|
+
...messages.slice(0, index),
|
|
3936
|
+
{ role: "user", content: [...textParts, ...parts] },
|
|
3937
|
+
...messages.slice(index + 1)
|
|
3938
|
+
];
|
|
3939
|
+
}
|
|
3940
|
+
return [...messages, { role: "user", content: [...parts] }];
|
|
3941
|
+
}
|
|
3942
|
+
async function extractErrorDetail(response) {
|
|
3943
|
+
try {
|
|
3944
|
+
const body = await response.json();
|
|
3945
|
+
return body.error;
|
|
3946
|
+
} catch {
|
|
3947
|
+
return void 0;
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
async function* parseSseEvent(rawEvent, toolCallAccumulator) {
|
|
3951
|
+
for (const line of rawEvent.split("\n")) {
|
|
3952
|
+
if (!line.startsWith("data:")) continue;
|
|
3953
|
+
const payload = line.slice(5).trim();
|
|
3954
|
+
if (payload.length === 0 || payload === "[DONE]") continue;
|
|
3955
|
+
let chunk;
|
|
3956
|
+
try {
|
|
3957
|
+
chunk = JSON.parse(payload);
|
|
3958
|
+
} catch {
|
|
3959
|
+
continue;
|
|
3960
|
+
}
|
|
3961
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
3962
|
+
if (delta?.reasoning) {
|
|
3963
|
+
yield { type: "delta", channel: "reasoning", text: delta.reasoning };
|
|
3964
|
+
}
|
|
3965
|
+
if (delta?.content) {
|
|
3966
|
+
yield { type: "delta", channel: "answer", text: delta.content };
|
|
3967
|
+
}
|
|
3968
|
+
if (delta?.tool_calls) {
|
|
3969
|
+
for (const toolCallDelta of delta.tool_calls) toolCallAccumulator.push(toolCallDelta);
|
|
3970
|
+
}
|
|
3971
|
+
if (chunk.usage) {
|
|
3972
|
+
yield {
|
|
3973
|
+
type: "usage",
|
|
3974
|
+
usage: {
|
|
3975
|
+
inputTokens: chunk.usage.prompt_tokens ?? 0,
|
|
3976
|
+
outputTokens: chunk.usage.completion_tokens ?? 0,
|
|
3977
|
+
reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens
|
|
3978
|
+
}
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
async function* parseSseStream(body) {
|
|
3984
|
+
const reader = body.getReader();
|
|
3985
|
+
const decoder = new TextDecoder();
|
|
3986
|
+
const toolCallAccumulator = new ToolCallAccumulator();
|
|
3987
|
+
let buffer = "";
|
|
3988
|
+
try {
|
|
3989
|
+
while (true) {
|
|
3990
|
+
const { done, value } = await reader.read();
|
|
3991
|
+
if (done) break;
|
|
3992
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3993
|
+
let separator = buffer.indexOf("\n\n");
|
|
3994
|
+
while (separator !== -1) {
|
|
3995
|
+
const rawEvent = buffer.slice(0, separator);
|
|
3996
|
+
buffer = buffer.slice(separator + 2);
|
|
3997
|
+
separator = buffer.indexOf("\n\n");
|
|
3998
|
+
yield* parseSseEvent(rawEvent, toolCallAccumulator);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
} catch (err) {
|
|
4002
|
+
throw errorFromUnknown(err);
|
|
4003
|
+
} finally {
|
|
4004
|
+
reader.releaseLock();
|
|
4005
|
+
}
|
|
4006
|
+
const calls = toolCallAccumulator.finish();
|
|
4007
|
+
if (calls.length > 0) yield { type: "tool_calls", calls };
|
|
4008
|
+
}
|
|
4009
|
+
function createOpenAiCompatibleAdapter(config) {
|
|
4010
|
+
if (!config.baseUrl && !config.endpointUrl) {
|
|
4011
|
+
throw new Error("createOpenAiCompatibleAdapter requires baseUrl or endpointUrl");
|
|
4012
|
+
}
|
|
4013
|
+
const endpoint = config.endpointUrl ?? `${config.baseUrl}/chat/completions`;
|
|
4014
|
+
return {
|
|
4015
|
+
async *stream(req) {
|
|
4016
|
+
const timeout = AbortSignal.timeout(config.timeoutMs);
|
|
4017
|
+
const signal = req.signal ? AbortSignal.any([req.signal, timeout]) : timeout;
|
|
4018
|
+
let response;
|
|
4019
|
+
try {
|
|
4020
|
+
response = await fetch(endpoint, {
|
|
4021
|
+
method: "POST",
|
|
4022
|
+
headers: {
|
|
4023
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
4024
|
+
"Content-Type": "application/json"
|
|
4025
|
+
},
|
|
4026
|
+
body: JSON.stringify({
|
|
4027
|
+
model: req.externalModelId,
|
|
4028
|
+
messages: mergeImages(req.messages.map(toWireMessage), req.images ?? []),
|
|
4029
|
+
temperature: req.temperature,
|
|
4030
|
+
max_completion_tokens: req.maxOutputTokens,
|
|
4031
|
+
top_p: req.topP,
|
|
4032
|
+
stream: true,
|
|
4033
|
+
stream_options: { include_usage: true },
|
|
4034
|
+
...req.tools && req.tools.length > 0 ? {
|
|
4035
|
+
tools: req.tools.map((tool) => ({ type: "function", function: tool })),
|
|
4036
|
+
tool_choice: "auto"
|
|
4037
|
+
} : {}
|
|
4038
|
+
}),
|
|
4039
|
+
signal
|
|
4040
|
+
});
|
|
4041
|
+
} catch (err) {
|
|
4042
|
+
throw errorFromUnknown(err);
|
|
4043
|
+
}
|
|
4044
|
+
if (!response.ok || !response.body) {
|
|
4045
|
+
const detail = await extractErrorDetail(response);
|
|
4046
|
+
throw errorFromStatus(response.status, detail?.message, detail);
|
|
4047
|
+
}
|
|
4048
|
+
yield* parseSseStream(response.body);
|
|
4049
|
+
}
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
4052
|
+
|
|
4053
|
+
// src/providers/openai-compatible/openai-compatible.provider.ts
|
|
4054
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
4055
|
+
var STRUCTURED_TEMPERATURE2 = 0.1;
|
|
4056
|
+
var OpenAiCompatibleProvider = class {
|
|
4057
|
+
constructor(config) {
|
|
4058
|
+
this.config = config;
|
|
4059
|
+
if (!config.baseUrl && !config.endpointUrl) {
|
|
4060
|
+
throw new AIError(
|
|
4061
|
+
"MISSING_CONFIG" /* MISSING_CONFIG */,
|
|
4062
|
+
"OpenAiCompatibleProvider requires baseUrl or endpointUrl"
|
|
4063
|
+
);
|
|
4064
|
+
}
|
|
4065
|
+
this.adapter = createOpenAiCompatibleAdapter({
|
|
4066
|
+
baseUrl: config.baseUrl,
|
|
4067
|
+
endpointUrl: config.endpointUrl,
|
|
4068
|
+
apiKey: config.apiKey,
|
|
4069
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
4070
|
+
});
|
|
4071
|
+
this.tiers = mergeTierOverrides(
|
|
4072
|
+
config.model ? { fast: config.model, balanced: config.model, powerful: config.model } : {},
|
|
4073
|
+
config.models
|
|
4074
|
+
);
|
|
4075
|
+
}
|
|
4076
|
+
config;
|
|
4077
|
+
id = "openai-compatible";
|
|
4078
|
+
name = "OpenAI-compatible";
|
|
4079
|
+
capabilities = ["text"];
|
|
4080
|
+
adapter;
|
|
4081
|
+
tiers;
|
|
4082
|
+
jsonParser = new JsonResponseParser();
|
|
4083
|
+
resolveModel(tier) {
|
|
4084
|
+
return resolveModel(this.tiers, tier, this.id);
|
|
4085
|
+
}
|
|
4086
|
+
async healthCheck() {
|
|
4087
|
+
return this.config.apiKey && (this.config.baseUrl || this.config.endpointUrl) ? "healthy" : "unavailable";
|
|
4088
|
+
}
|
|
4089
|
+
async estimateCost() {
|
|
4090
|
+
return 0;
|
|
4091
|
+
}
|
|
4092
|
+
async generate(request) {
|
|
4093
|
+
const model = this.modelFor(request);
|
|
4094
|
+
const outcome = await this.consume(this.toAdapterRequest(model, request));
|
|
4095
|
+
if (outcome.text.length === 0) {
|
|
4096
|
+
throw this.noContentError(model);
|
|
4097
|
+
}
|
|
4098
|
+
return {
|
|
4099
|
+
text: outcome.text,
|
|
4100
|
+
usage: outcome.usage,
|
|
4101
|
+
model,
|
|
4102
|
+
providerId: this.id,
|
|
4103
|
+
finishReason: "stop"
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
async generateStructured(request) {
|
|
4107
|
+
const model = this.modelFor(request);
|
|
4108
|
+
const messages = this.toWireMessages(request.messages, request.systemPrompt);
|
|
4109
|
+
const lastUserIndex = findLastIndex4(messages, (m) => m.role === "user");
|
|
4110
|
+
if (lastUserIndex === -1) {
|
|
4111
|
+
throw new AIError(
|
|
4112
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
4113
|
+
"generateStructured requires at least one user message"
|
|
4114
|
+
);
|
|
4115
|
+
}
|
|
4116
|
+
const structuredPrompt = new StructuredPromptBuilder(
|
|
4117
|
+
messages[lastUserIndex]?.content ?? ""
|
|
4118
|
+
).withSchema(request.schema).build();
|
|
4119
|
+
messages[lastUserIndex] = { role: "user", content: structuredPrompt };
|
|
4120
|
+
const outcome = await this.consume({
|
|
4121
|
+
externalModelId: model,
|
|
4122
|
+
messages,
|
|
4123
|
+
temperature: STRUCTURED_TEMPERATURE2,
|
|
4124
|
+
signal: request.signal
|
|
4125
|
+
});
|
|
4126
|
+
if (outcome.text.length === 0) {
|
|
4127
|
+
throw this.noContentError(model);
|
|
4128
|
+
}
|
|
4129
|
+
return {
|
|
4130
|
+
value: this.jsonParser.parse(outcome.text),
|
|
4131
|
+
result: {
|
|
4132
|
+
text: outcome.text,
|
|
4133
|
+
usage: outcome.usage,
|
|
4134
|
+
model,
|
|
4135
|
+
providerId: this.id,
|
|
4136
|
+
finishReason: "stop"
|
|
4137
|
+
}
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
4140
|
+
async stream(request, callbacks) {
|
|
4141
|
+
const model = this.modelFor(request);
|
|
4142
|
+
try {
|
|
4143
|
+
const outcome = await this.consume(this.toAdapterRequest(model, request), (text) => {
|
|
4144
|
+
callbacks.onChunk?.(text);
|
|
4145
|
+
});
|
|
4146
|
+
if (outcome.text.length === 0) {
|
|
4147
|
+
throw this.noContentError(model);
|
|
4148
|
+
}
|
|
4149
|
+
const result = {
|
|
4150
|
+
text: outcome.text,
|
|
4151
|
+
usage: outcome.usage,
|
|
4152
|
+
model,
|
|
4153
|
+
providerId: this.id,
|
|
4154
|
+
finishReason: "stop"
|
|
4155
|
+
};
|
|
4156
|
+
callbacks.onComplete?.(result);
|
|
4157
|
+
return result;
|
|
4158
|
+
} catch (error) {
|
|
4159
|
+
callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
4160
|
+
throw error;
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
/* ---------- internals ---------- */
|
|
4164
|
+
/** Drain one adapter stream to its end. `onChunk`, when given, sees answer
|
|
4165
|
+
* deltas as they arrive — the streaming surface is this same drain. */
|
|
4166
|
+
async consume(req, onChunk) {
|
|
4167
|
+
let text = "";
|
|
4168
|
+
let usage;
|
|
4169
|
+
for await (const event of this.adapter.stream(req)) {
|
|
4170
|
+
if (event.type === "delta" && event.channel === "answer") {
|
|
4171
|
+
text += event.text;
|
|
4172
|
+
onChunk?.(event.text);
|
|
4173
|
+
} else if (event.type === "usage") {
|
|
4174
|
+
usage = event.usage;
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
return { text, usage };
|
|
4178
|
+
}
|
|
4179
|
+
modelFor(request) {
|
|
4180
|
+
return request.model ?? this.resolveModel(request.tier ?? "balanced");
|
|
4181
|
+
}
|
|
4182
|
+
noContentError(model) {
|
|
4183
|
+
return new AIError(
|
|
4184
|
+
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
4185
|
+
"No content generated from the OpenAI-compatible endpoint",
|
|
4186
|
+
void 0,
|
|
4187
|
+
{ model }
|
|
4188
|
+
);
|
|
4189
|
+
}
|
|
4190
|
+
toAdapterRequest(model, request) {
|
|
4191
|
+
return {
|
|
4192
|
+
externalModelId: model,
|
|
4193
|
+
messages: this.toWireMessages(request.messages, request.systemPrompt),
|
|
4194
|
+
...request.temperature !== void 0 ? { temperature: request.temperature } : {},
|
|
4195
|
+
...request.maxTokens !== void 0 ? { maxOutputTokens: request.maxTokens } : {},
|
|
4196
|
+
...request.topP !== void 0 ? { topP: request.topP } : {},
|
|
4197
|
+
...request.signal ? { signal: request.signal } : {}
|
|
4198
|
+
};
|
|
4199
|
+
}
|
|
4200
|
+
/** Neutral messages onto the adapter's shape; systemPrompt becomes the
|
|
4201
|
+
* leading system message (in-band, the OpenAI way). */
|
|
4202
|
+
toWireMessages(messages, systemPrompt) {
|
|
4203
|
+
const wire = [];
|
|
4204
|
+
if (systemPrompt && systemPrompt.length > 0) {
|
|
4205
|
+
wire.push({ role: "system", content: systemPrompt });
|
|
4206
|
+
}
|
|
4207
|
+
for (const message of messages) {
|
|
4208
|
+
wire.push({ role: message.role, content: message.content });
|
|
4209
|
+
}
|
|
4210
|
+
return wire;
|
|
4211
|
+
}
|
|
4212
|
+
};
|
|
4213
|
+
function findLastIndex4(items, predicate) {
|
|
4214
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
4215
|
+
if (predicate(items[index])) return index;
|
|
4216
|
+
}
|
|
4217
|
+
return -1;
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4220
|
+
// src/providers/workers-ai/workers-ai.entities.ts
|
|
4221
|
+
var WorkersAIError = class extends Error {
|
|
4222
|
+
status;
|
|
4223
|
+
constructor(message, status) {
|
|
4224
|
+
super(message);
|
|
4225
|
+
this.name = "WorkersAIError";
|
|
4226
|
+
this.status = status;
|
|
4227
|
+
}
|
|
4228
|
+
};
|
|
4229
|
+
|
|
4230
|
+
// src/providers/workers-ai/workers-ai.text.ts
|
|
4231
|
+
var WORKERS_AI_LLM_MODEL = "@cf/meta/llama-3.3-70b-instruct-fp8-fast";
|
|
4232
|
+
var WORKERS_AI_TEXT_TIMEOUT_MS = 25e3;
|
|
4233
|
+
function requireAi(env) {
|
|
4234
|
+
const ai = env.AI;
|
|
4235
|
+
if (!ai || typeof ai.run !== "function") throw new WorkersAIError("Workers AI is not configured");
|
|
4236
|
+
return ai;
|
|
4237
|
+
}
|
|
4238
|
+
async function runWorkersAIText(env, messages, opts = {}) {
|
|
4239
|
+
const ai = requireAi(env);
|
|
4240
|
+
const model = opts.model ?? WORKERS_AI_LLM_MODEL;
|
|
4241
|
+
let result;
|
|
4242
|
+
try {
|
|
4243
|
+
result = await Promise.race([
|
|
4244
|
+
ai.run(model, {
|
|
4245
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
4246
|
+
max_tokens: opts.maxTokens ?? 900,
|
|
4247
|
+
temperature: opts.temperature ?? 0.7
|
|
4248
|
+
}),
|
|
4249
|
+
new Promise(
|
|
4250
|
+
(_, reject) => setTimeout(() => reject(new WorkersAIError("Workers AI timed out")), WORKERS_AI_TEXT_TIMEOUT_MS)
|
|
4251
|
+
)
|
|
4252
|
+
]);
|
|
4253
|
+
} catch (err) {
|
|
4254
|
+
throw err instanceof WorkersAIError ? err : new WorkersAIError(err instanceof Error ? err.message : "Workers AI request failed");
|
|
4255
|
+
}
|
|
4256
|
+
const response = result ?? {};
|
|
4257
|
+
let text = response.response;
|
|
4258
|
+
if (typeof text !== "string" || text.length === 0) {
|
|
4259
|
+
text = response.choices?.[0]?.message?.content;
|
|
4260
|
+
}
|
|
4261
|
+
if (typeof text !== "string" || text.length === 0) {
|
|
4262
|
+
throw new WorkersAIError("Workers AI returned no text");
|
|
4263
|
+
}
|
|
4264
|
+
return text;
|
|
4265
|
+
}
|
|
4266
|
+
async function* streamWorkersAIText(env, messages, opts = {}) {
|
|
4267
|
+
const ai = requireAi(env);
|
|
4268
|
+
const model = opts.model ?? WORKERS_AI_LLM_MODEL;
|
|
4269
|
+
let body;
|
|
4270
|
+
try {
|
|
4271
|
+
body = await Promise.race([
|
|
4272
|
+
ai.run(model, {
|
|
4273
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
4274
|
+
stream: true,
|
|
4275
|
+
max_tokens: opts.maxTokens ?? 600,
|
|
4276
|
+
temperature: opts.temperature ?? 0.7
|
|
4277
|
+
}),
|
|
4278
|
+
new Promise(
|
|
4279
|
+
(_, reject) => setTimeout(() => reject(new WorkersAIError("Workers AI timed out")), WORKERS_AI_TEXT_TIMEOUT_MS)
|
|
4280
|
+
)
|
|
4281
|
+
]);
|
|
4282
|
+
} catch (err) {
|
|
4283
|
+
throw err instanceof WorkersAIError ? err : new WorkersAIError(err instanceof Error ? err.message : "Workers AI request failed");
|
|
4284
|
+
}
|
|
4285
|
+
const stream = body;
|
|
4286
|
+
const getReader = typeof stream === "object" && stream !== null && "getReader" in stream && typeof stream.getReader === "function" ? stream.getReader : void 0;
|
|
4287
|
+
if (!getReader) throw new WorkersAIError("Workers AI stream was not readable");
|
|
4288
|
+
const reader = getReader.call(stream);
|
|
4289
|
+
const decoder = new TextDecoder();
|
|
4290
|
+
let buffer = "";
|
|
4291
|
+
try {
|
|
4292
|
+
for (; ; ) {
|
|
4293
|
+
const { done, value } = await reader.read();
|
|
4294
|
+
if (done) break;
|
|
4295
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4296
|
+
let newlineAt;
|
|
4297
|
+
while ((newlineAt = buffer.indexOf("\n")) !== -1) {
|
|
4298
|
+
let line = buffer.slice(0, newlineAt);
|
|
4299
|
+
buffer = buffer.slice(newlineAt + 1);
|
|
4300
|
+
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
4301
|
+
if (line.startsWith(":") || line.trim() === "") continue;
|
|
4302
|
+
if (!line.startsWith("data: ")) continue;
|
|
4303
|
+
const payload = line.slice(6).trim();
|
|
4304
|
+
if (payload === "[DONE]") return;
|
|
4305
|
+
try {
|
|
4306
|
+
const parsed = JSON.parse(payload);
|
|
4307
|
+
if (typeof parsed.response === "string" && parsed.response.length > 0) yield parsed.response;
|
|
4308
|
+
} catch {
|
|
4309
|
+
buffer = `${line}
|
|
4310
|
+
${buffer}`;
|
|
4311
|
+
break;
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
} finally {
|
|
4316
|
+
reader.releaseLock();
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
// src/providers/workers-ai/workers-ai.provider.ts
|
|
4321
|
+
var WORKERS_AI_TIERS = {
|
|
4322
|
+
fast: "@cf/meta/llama-3.1-8b-instruct-fp8-fast",
|
|
4323
|
+
balanced: WORKERS_AI_LLM_MODEL,
|
|
4324
|
+
powerful: WORKERS_AI_LLM_MODEL
|
|
4325
|
+
};
|
|
4326
|
+
var WorkersAIProvider = class {
|
|
4327
|
+
id = "workers-ai";
|
|
4328
|
+
name = "Cloudflare Workers AI";
|
|
4329
|
+
capabilities = ["text"];
|
|
4330
|
+
binding;
|
|
4331
|
+
tiers;
|
|
4332
|
+
jsonParser = new JsonResponseParser();
|
|
4333
|
+
constructor(config) {
|
|
4334
|
+
this.binding = config.binding;
|
|
4335
|
+
this.tiers = mergeTierOverrides(WORKERS_AI_TIERS, config.models);
|
|
4336
|
+
}
|
|
4337
|
+
resolveModel(tier) {
|
|
4338
|
+
return resolveModel(this.tiers, tier, this.id);
|
|
4339
|
+
}
|
|
4340
|
+
async healthCheck() {
|
|
4341
|
+
return typeof this.binding?.run === "function" ? "healthy" : "unavailable";
|
|
4342
|
+
}
|
|
4343
|
+
/** Workers AI is allocation-priced (neurons per deployment), not per
|
|
4344
|
+
* request — there is no per-call cost to estimate. */
|
|
4345
|
+
async estimateCost(_request) {
|
|
4346
|
+
return 0;
|
|
4347
|
+
}
|
|
4348
|
+
async generate(request) {
|
|
4349
|
+
const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
|
|
4350
|
+
const messages = withSystemPrompt(request);
|
|
4351
|
+
try {
|
|
4352
|
+
const text = await runWorkersAIText(
|
|
4353
|
+
{ AI: this.binding },
|
|
4354
|
+
messages,
|
|
4355
|
+
{
|
|
4356
|
+
model,
|
|
4357
|
+
maxTokens: request.maxTokens,
|
|
4358
|
+
temperature: request.temperature
|
|
4359
|
+
}
|
|
4360
|
+
);
|
|
4361
|
+
return { text, model, providerId: this.id, finishReason: "stop" };
|
|
4362
|
+
} catch (error) {
|
|
4363
|
+
throw toAIError(error);
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
async generateStructured(request) {
|
|
4367
|
+
const messages = [...request.messages];
|
|
4368
|
+
const lastUserIndex = findLastIndex5(messages, (m) => m.role === "user");
|
|
4369
|
+
if (lastUserIndex === -1) {
|
|
4370
|
+
throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "generateStructured requires at least one user message");
|
|
4371
|
+
}
|
|
4372
|
+
messages[lastUserIndex] = {
|
|
4373
|
+
role: "user",
|
|
4374
|
+
content: new StructuredPromptBuilder(messages[lastUserIndex]?.content ?? "").withSchema(request.schema).build()
|
|
4375
|
+
};
|
|
4376
|
+
const result = await this.generate({ ...request, messages, temperature: 0.1 });
|
|
4377
|
+
return { value: this.jsonParser.parse(result.text), result };
|
|
4378
|
+
}
|
|
4379
|
+
async stream(request, callbacks) {
|
|
4380
|
+
const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
|
|
4381
|
+
const messages = withSystemPrompt(request);
|
|
4382
|
+
let text = "";
|
|
4383
|
+
try {
|
|
4384
|
+
for await (const chunk of streamWorkersAIText({ AI: this.binding }, messages, {
|
|
4385
|
+
model,
|
|
4386
|
+
maxTokens: request.maxTokens,
|
|
4387
|
+
temperature: request.temperature
|
|
4388
|
+
})) {
|
|
4389
|
+
text += chunk;
|
|
4390
|
+
callbacks.onChunk?.(chunk);
|
|
4391
|
+
}
|
|
4392
|
+
} catch (error) {
|
|
4393
|
+
const aiError = toAIError(error);
|
|
4394
|
+
callbacks.onError?.(aiError);
|
|
4395
|
+
throw aiError;
|
|
4396
|
+
}
|
|
4397
|
+
const result = { text, model, providerId: this.id, finishReason: "stop" };
|
|
4398
|
+
callbacks.onComplete?.(result);
|
|
4399
|
+
return result;
|
|
4400
|
+
}
|
|
4401
|
+
};
|
|
4402
|
+
function withSystemPrompt(request) {
|
|
4403
|
+
if (request.systemPrompt && request.systemPrompt.length > 0) {
|
|
4404
|
+
return [
|
|
4405
|
+
{ role: "system", content: request.systemPrompt },
|
|
4406
|
+
...request.messages.filter((m) => m.role !== "system")
|
|
4407
|
+
];
|
|
4408
|
+
}
|
|
4409
|
+
return request.messages;
|
|
4410
|
+
}
|
|
4411
|
+
function toAIError(error) {
|
|
4412
|
+
if (error instanceof AIError) return error;
|
|
4413
|
+
const message = error instanceof Error ? error.message : "Workers AI request failed";
|
|
4414
|
+
const type = error instanceof WorkersAIError && error.message.includes("not configured") ? "INVALID_API_KEY" /* INVALID_API_KEY */ : "SERVER_ERROR" /* SERVER_ERROR */;
|
|
4415
|
+
return new AIError(type, message, error instanceof Error ? error : void 0);
|
|
4416
|
+
}
|
|
4417
|
+
function findLastIndex5(items, predicate) {
|
|
4418
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
4419
|
+
const item = items[i];
|
|
4420
|
+
if (item !== void 0 && predicate(item)) return i;
|
|
4421
|
+
}
|
|
4422
|
+
return -1;
|
|
4423
|
+
}
|
|
4424
|
+
|
|
3820
4425
|
// src/providers/factory/create-ai.ts
|
|
3821
4426
|
function createAI(config = {}) {
|
|
3822
4427
|
const factory = new ProviderFactory({
|
|
@@ -3839,6 +4444,15 @@ function createAI(config = {}) {
|
|
|
3839
4444
|
if (config.pruna?.apiKey) {
|
|
3840
4445
|
factory.register(new PrunaProvider({ ...config.pruna, enabled: true }));
|
|
3841
4446
|
}
|
|
4447
|
+
const openaiCompatible = config.openaiCompatible;
|
|
4448
|
+
if (openaiCompatible?.apiKey && (openaiCompatible.baseUrl || openaiCompatible.endpointUrl)) {
|
|
4449
|
+
factory.register(
|
|
4450
|
+
new OpenAiCompatibleProvider({ ...openaiCompatible, apiKey: openaiCompatible.apiKey })
|
|
4451
|
+
);
|
|
4452
|
+
}
|
|
4453
|
+
if (config.workersAI?.binding) {
|
|
4454
|
+
factory.register(new WorkersAIProvider(config.workersAI));
|
|
4455
|
+
}
|
|
3842
4456
|
return factory;
|
|
3843
4457
|
}
|
|
3844
4458
|
|
|
@@ -4351,7 +4965,7 @@ async function batchRequest(requests, options = {}) {
|
|
|
4351
4965
|
errors.push(result.reason);
|
|
4352
4966
|
if (stopOnError) {
|
|
4353
4967
|
throw new Error(
|
|
4354
|
-
`Batch request failed: ${errors[0]
|
|
4968
|
+
`Batch request failed: ${errors[0]?.message}`
|
|
4355
4969
|
);
|
|
4356
4970
|
}
|
|
4357
4971
|
}
|
|
@@ -4813,7 +5427,7 @@ function safeJSONParse(text, fallback, onError) {
|
|
|
4813
5427
|
}
|
|
4814
5428
|
if (error instanceof SyntaxError) {
|
|
4815
5429
|
const positionMatch = error.message.match(/position (\d+)/);
|
|
4816
|
-
const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
|
|
5430
|
+
const position = positionMatch ? parseInt(positionMatch[1] ?? "", 10) : -1;
|
|
4817
5431
|
if (position > 0) {
|
|
4818
5432
|
const context = text.substring(Math.max(0, position - 50), position + 50);
|
|
4819
5433
|
onError?.(`JSON parse error at position ${position}: "${context}"`);
|