@umituz/web-ai 1.0.11 → 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.
Files changed (48) hide show
  1. package/README.md +29 -3
  2. package/dist/{cancellation-error-_39nYWwz.d.cts → cancellation-error-BWb59Wkj.d.cts} +1 -1
  3. package/dist/{cancellation-error-hYcE9AS9.d.ts → cancellation-error-D9LETLCD.d.ts} +1 -1
  4. package/dist/chat/index.cjs +1 -0
  5. package/dist/chat/index.cjs.map +1 -1
  6. package/dist/chat/index.js +1 -0
  7. package/dist/chat/index.js.map +1 -1
  8. package/dist/content/index.cjs +622 -8
  9. package/dist/content/index.cjs.map +1 -1
  10. package/dist/content/index.d.cts +2 -2
  11. package/dist/content/index.d.ts +2 -2
  12. package/dist/content/index.js +622 -8
  13. package/dist/content/index.js.map +1 -1
  14. package/dist/context-manager-CuZz0vnC.d.cts +104 -0
  15. package/dist/context-manager-CuZz0vnC.d.ts +104 -0
  16. package/dist/core/index.cjs +147 -10
  17. package/dist/core/index.cjs.map +1 -1
  18. package/dist/core/index.d.cts +91 -73
  19. package/dist/core/index.d.ts +91 -73
  20. package/dist/core/index.js +141 -11
  21. package/dist/core/index.js.map +1 -1
  22. package/dist/{create-ai-D-oEWzf9.d.ts → create-ai-Cq3HkJxd.d.ts} +61 -1
  23. package/dist/{create-ai-DRysRImc.d.cts → create-ai-DDyTVzef.d.cts} +61 -1
  24. package/dist/hooks/index.cjs +747 -15
  25. package/dist/hooks/index.cjs.map +1 -1
  26. package/dist/hooks/index.d.cts +57 -4
  27. package/dist/hooks/index.d.ts +57 -4
  28. package/dist/hooks/index.js +746 -16
  29. package/dist/hooks/index.js.map +1 -1
  30. package/dist/index.cjs +1180 -242
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +7 -6
  33. package/dist/index.d.ts +7 -6
  34. package/dist/index.js +1140 -243
  35. package/dist/index.js.map +1 -1
  36. package/dist/providers/index.cjs +1212 -362
  37. package/dist/providers/index.cjs.map +1 -1
  38. package/dist/providers/index.d.cts +422 -10
  39. package/dist/providers/index.d.ts +422 -10
  40. package/dist/providers/index.js +1179 -363
  41. package/dist/providers/index.js.map +1 -1
  42. package/dist/remote-chat-client-LrNlew4n.d.cts +70 -0
  43. package/dist/remote-chat-client-LrNlew4n.d.ts +70 -0
  44. package/dist/{requestQueue-D89wx91Q.d.cts → requestQueue--h2uZ-Q3.d.cts} +1 -1
  45. package/dist/{requestQueue-AP6n_bRH.d.ts → requestQueue-BGeCy4Fd.d.ts} +1 -1
  46. package/package.json +1 -1
  47. package/dist/tool-call-accumulator-C7KhaXZA.d.cts +0 -69
  48. package/dist/tool-call-accumulator-C7KhaXZA.d.ts +0 -69
@@ -203,6 +203,68 @@ async function extractErrorMessageFromResponse(response) {
203
203
  return buildHttpStatusFallback(response.status, response.statusText);
204
204
  }
205
205
 
206
+ // src/core/errors/unified-ai-error.ts
207
+ var STATUS_TO_CODE = {
208
+ 400: "invalid_request",
209
+ 401: "invalid_key",
210
+ 403: "invalid_key",
211
+ 404: "model_unavailable",
212
+ 413: "invalid_request",
213
+ 422: "invalid_request",
214
+ 429: "rate_limited",
215
+ 500: "upstream_error",
216
+ 502: "upstream_error",
217
+ 503: "capacity",
218
+ 504: "timeout"
219
+ };
220
+ var CODE_MESSAGES = {
221
+ invalid_key: "The provider rejected the configured API key.",
222
+ missing_key: "The provider is not configured with an API key.",
223
+ rate_limited: "The provider rate limit was reached. Try again shortly.",
224
+ capacity: "The provider is temporarily at capacity. Try again shortly.",
225
+ timeout: "The model took too long to respond.",
226
+ invalid_request: "The request was rejected by the provider.",
227
+ model_unavailable: "The requested model is not available.",
228
+ upstream_error: "The provider returned an unexpected error.",
229
+ network: "Could not reach the provider.",
230
+ cancelled: "Generation was cancelled."
231
+ };
232
+ var AiError = class extends Error {
233
+ code;
234
+ constructor(code, message) {
235
+ super(message ?? CODE_MESSAGES[code]);
236
+ this.name = "AiError";
237
+ this.code = code;
238
+ }
239
+ get userMessage() {
240
+ return this.message;
241
+ }
242
+ };
243
+ function isApiKeyRejection(provider) {
244
+ if (provider.code === "invalid_api_key") return true;
245
+ return provider.type === void 0;
246
+ }
247
+ function errorFromStatus(status, detail, provider) {
248
+ let code = STATUS_TO_CODE[status] ?? "upstream_error";
249
+ if (status === 403 && provider && !isApiKeyRejection(provider)) {
250
+ code = "model_unavailable";
251
+ }
252
+ return new AiError(code, detail ? `${CODE_MESSAGES[code]} (${detail})` : void 0);
253
+ }
254
+ function errorFromUnknown(err) {
255
+ if (err instanceof AiError) return err;
256
+ if (err instanceof Error) {
257
+ if (err.name === "TimeoutError" || err.name === "AbortError") {
258
+ return new AiError("timeout");
259
+ }
260
+ if (err.name === "TypeError") {
261
+ return new AiError("network");
262
+ }
263
+ return new AiError("upstream_error", err.message);
264
+ }
265
+ return new AiError("upstream_error");
266
+ }
267
+
206
268
  // src/providers/base/provider.types.ts
207
269
  function isTextProvider(provider) {
208
270
  return provider.capabilities.includes("text");
@@ -969,7 +1031,7 @@ var AnthropicProvider = class {
969
1031
  );
970
1032
  }
971
1033
  const structuredPrompt = new StructuredPromptBuilder(
972
- messages[lastUserIndex].content
1034
+ messages[lastUserIndex]?.content ?? ""
973
1035
  ).withSchema(request.schema).build();
974
1036
  messages[lastUserIndex] = { role: "user", content: structuredPrompt };
975
1037
  const wireRequest = new AnthropicRequestBuilder(model).withRequest({ ...request, messages }).withEffort(this.config.effort).build();
@@ -1049,7 +1111,8 @@ function mapAnthropicUsage(usage) {
1049
1111
  }
1050
1112
  function findLastIndex(items, predicate) {
1051
1113
  for (let i = items.length - 1; i >= 0; i--) {
1052
- if (predicate(items[i])) return i;
1114
+ const item = items[i];
1115
+ if (item !== void 0 && predicate(item)) return i;
1053
1116
  }
1054
1117
  return -1;
1055
1118
  }
@@ -1896,7 +1959,7 @@ var GroqProvider = class {
1896
1959
  );
1897
1960
  }
1898
1961
  const structuredPrompt = new StructuredPromptBuilder(
1899
- messages[lastUserIndex].content
1962
+ messages[lastUserIndex]?.content ?? ""
1900
1963
  ).withSchema(request.schema).build();
1901
1964
  messages[lastUserIndex] = { role: "user", content: structuredPrompt };
1902
1965
  const wireRequest = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(this.generationConfig(request, STRUCTURED_TEMPERATURE)).withJsonResponse(true).build();
@@ -1990,7 +2053,8 @@ function toGroqMessages(messages, systemPrompt) {
1990
2053
  }
1991
2054
  function findLastIndex2(items, predicate) {
1992
2055
  for (let i = items.length - 1; i >= 0; i--) {
1993
- if (predicate(items[i])) return i;
2056
+ const item = items[i];
2057
+ if (item !== void 0 && predicate(item)) return i;
1994
2058
  }
1995
2059
  return -1;
1996
2060
  }
@@ -2205,7 +2269,7 @@ var GeminiProvider = class extends BaseProvider {
2205
2269
  }
2206
2270
  messages[lastUserIndex] = {
2207
2271
  role: "user",
2208
- content: new StructuredPromptBuilder(messages[lastUserIndex].content).withSchema(request.schema).build()
2272
+ content: new StructuredPromptBuilder(messages[lastUserIndex]?.content ?? "").withSchema(request.schema).build()
2209
2273
  };
2210
2274
  const result = await this.generate({ ...request, messages, temperature: 0.1 });
2211
2275
  return { value: this.jsonParser.parse(result.text), result };
@@ -2311,7 +2375,8 @@ function mapGeminiFinishReason(reason) {
2311
2375
  }
2312
2376
  function findLastIndex3(items, predicate) {
2313
2377
  for (let i = items.length - 1; i >= 0; i--) {
2314
- if (predicate(items[i])) return i;
2378
+ const item = items[i];
2379
+ if (item !== void 0 && predicate(item)) return i;
2315
2380
  }
2316
2381
  return -1;
2317
2382
  }
@@ -3517,6 +3582,546 @@ var PrunaProvider = class {
3517
3582
  }
3518
3583
  };
3519
3584
 
3585
+ // src/core/sse/tool-call-accumulator.ts
3586
+ var ToolCallAccumulator = class {
3587
+ slots = /* @__PURE__ */ new Map();
3588
+ push(delta) {
3589
+ const index = delta.index ?? 0;
3590
+ const slot = this.slots.get(index) ?? { id: "", name: "", args: "" };
3591
+ if (delta.id) slot.id = delta.id;
3592
+ if (delta.function?.name) slot.name = delta.function.name;
3593
+ if (delta.function?.arguments) slot.args += delta.function.arguments;
3594
+ this.slots.set(index, slot);
3595
+ }
3596
+ finish() {
3597
+ return Array.from(this.slots.entries()).sort(([a], [b]) => a - b).map(([, slot]) => ({
3598
+ id: slot.id,
3599
+ name: slot.name,
3600
+ arguments: slot.args
3601
+ })).filter((call) => call.id.length > 0 || call.name.length > 0);
3602
+ }
3603
+ };
3604
+
3605
+ // src/providers/adapters/openai-compatible.adapter.ts
3606
+ function toWireMessage(message) {
3607
+ if (message.role === "tool") {
3608
+ return { role: "tool", content: message.content, tool_call_id: message.toolCallId ?? "" };
3609
+ }
3610
+ if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
3611
+ return {
3612
+ role: "assistant",
3613
+ content: message.content,
3614
+ tool_calls: message.toolCalls.map((call) => ({
3615
+ id: call.id,
3616
+ type: "function",
3617
+ function: { name: call.name, arguments: call.arguments }
3618
+ }))
3619
+ };
3620
+ }
3621
+ if (message.role === "user") return { role: "user", content: message.content };
3622
+ return { role: message.role, content: message.content };
3623
+ }
3624
+ function mergeImages(messages, images) {
3625
+ if (images.length === 0) return [...messages];
3626
+ const parts = images.map((image) => ({
3627
+ type: "image_url",
3628
+ image_url: { url: `data:${image.mimeType};base64,${image.dataBase64}` }
3629
+ }));
3630
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
3631
+ const message = messages[index];
3632
+ if (!message || message.role !== "user") continue;
3633
+ const textParts = typeof message.content === "string" ? message.content.length > 0 ? [{ type: "text", text: message.content }] : [] : message.content;
3634
+ return [
3635
+ ...messages.slice(0, index),
3636
+ { role: "user", content: [...textParts, ...parts] },
3637
+ ...messages.slice(index + 1)
3638
+ ];
3639
+ }
3640
+ return [...messages, { role: "user", content: [...parts] }];
3641
+ }
3642
+ async function extractErrorDetail(response) {
3643
+ try {
3644
+ const body = await response.json();
3645
+ return body.error;
3646
+ } catch {
3647
+ return void 0;
3648
+ }
3649
+ }
3650
+ async function* parseSseEvent(rawEvent, toolCallAccumulator) {
3651
+ for (const line of rawEvent.split("\n")) {
3652
+ if (!line.startsWith("data:")) continue;
3653
+ const payload = line.slice(5).trim();
3654
+ if (payload.length === 0 || payload === "[DONE]") continue;
3655
+ let chunk;
3656
+ try {
3657
+ chunk = JSON.parse(payload);
3658
+ } catch {
3659
+ continue;
3660
+ }
3661
+ const delta = chunk.choices?.[0]?.delta;
3662
+ if (delta?.reasoning) {
3663
+ yield { type: "delta", channel: "reasoning", text: delta.reasoning };
3664
+ }
3665
+ if (delta?.content) {
3666
+ yield { type: "delta", channel: "answer", text: delta.content };
3667
+ }
3668
+ if (delta?.tool_calls) {
3669
+ for (const toolCallDelta of delta.tool_calls) toolCallAccumulator.push(toolCallDelta);
3670
+ }
3671
+ if (chunk.usage) {
3672
+ yield {
3673
+ type: "usage",
3674
+ usage: {
3675
+ inputTokens: chunk.usage.prompt_tokens ?? 0,
3676
+ outputTokens: chunk.usage.completion_tokens ?? 0,
3677
+ reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens
3678
+ }
3679
+ };
3680
+ }
3681
+ }
3682
+ }
3683
+ async function* parseSseStream(body) {
3684
+ const reader = body.getReader();
3685
+ const decoder = new TextDecoder();
3686
+ const toolCallAccumulator = new ToolCallAccumulator();
3687
+ let buffer = "";
3688
+ try {
3689
+ while (true) {
3690
+ const { done, value } = await reader.read();
3691
+ if (done) break;
3692
+ buffer += decoder.decode(value, { stream: true });
3693
+ let separator = buffer.indexOf("\n\n");
3694
+ while (separator !== -1) {
3695
+ const rawEvent = buffer.slice(0, separator);
3696
+ buffer = buffer.slice(separator + 2);
3697
+ separator = buffer.indexOf("\n\n");
3698
+ yield* parseSseEvent(rawEvent, toolCallAccumulator);
3699
+ }
3700
+ }
3701
+ } catch (err) {
3702
+ throw errorFromUnknown(err);
3703
+ } finally {
3704
+ reader.releaseLock();
3705
+ }
3706
+ const calls = toolCallAccumulator.finish();
3707
+ if (calls.length > 0) yield { type: "tool_calls", calls };
3708
+ }
3709
+ function createOpenAiCompatibleAdapter(config) {
3710
+ if (!config.baseUrl && !config.endpointUrl) {
3711
+ throw new Error("createOpenAiCompatibleAdapter requires baseUrl or endpointUrl");
3712
+ }
3713
+ const endpoint = config.endpointUrl ?? `${config.baseUrl}/chat/completions`;
3714
+ return {
3715
+ async *stream(req) {
3716
+ const timeout = AbortSignal.timeout(config.timeoutMs);
3717
+ const signal = req.signal ? AbortSignal.any([req.signal, timeout]) : timeout;
3718
+ let response;
3719
+ try {
3720
+ response = await fetch(endpoint, {
3721
+ method: "POST",
3722
+ headers: {
3723
+ Authorization: `Bearer ${config.apiKey}`,
3724
+ "Content-Type": "application/json"
3725
+ },
3726
+ body: JSON.stringify({
3727
+ model: req.externalModelId,
3728
+ messages: mergeImages(req.messages.map(toWireMessage), req.images ?? []),
3729
+ temperature: req.temperature,
3730
+ max_completion_tokens: req.maxOutputTokens,
3731
+ top_p: req.topP,
3732
+ stream: true,
3733
+ stream_options: { include_usage: true },
3734
+ ...req.tools && req.tools.length > 0 ? {
3735
+ tools: req.tools.map((tool) => ({ type: "function", function: tool })),
3736
+ tool_choice: "auto"
3737
+ } : {}
3738
+ }),
3739
+ signal
3740
+ });
3741
+ } catch (err) {
3742
+ throw errorFromUnknown(err);
3743
+ }
3744
+ if (!response.ok || !response.body) {
3745
+ const detail = await extractErrorDetail(response);
3746
+ throw errorFromStatus(response.status, detail?.message, detail);
3747
+ }
3748
+ yield* parseSseStream(response.body);
3749
+ }
3750
+ };
3751
+ }
3752
+
3753
+ // src/providers/openai-compatible/openai-compatible.provider.ts
3754
+ var DEFAULT_TIMEOUT_MS = 3e4;
3755
+ var STRUCTURED_TEMPERATURE2 = 0.1;
3756
+ var OpenAiCompatibleProvider = class {
3757
+ constructor(config) {
3758
+ this.config = config;
3759
+ if (!config.baseUrl && !config.endpointUrl) {
3760
+ throw new AIError(
3761
+ "MISSING_CONFIG" /* MISSING_CONFIG */,
3762
+ "OpenAiCompatibleProvider requires baseUrl or endpointUrl"
3763
+ );
3764
+ }
3765
+ this.adapter = createOpenAiCompatibleAdapter({
3766
+ baseUrl: config.baseUrl,
3767
+ endpointUrl: config.endpointUrl,
3768
+ apiKey: config.apiKey,
3769
+ timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS
3770
+ });
3771
+ this.tiers = mergeTierOverrides(
3772
+ config.model ? { fast: config.model, balanced: config.model, powerful: config.model } : {},
3773
+ config.models
3774
+ );
3775
+ }
3776
+ config;
3777
+ id = "openai-compatible";
3778
+ name = "OpenAI-compatible";
3779
+ capabilities = ["text"];
3780
+ adapter;
3781
+ tiers;
3782
+ jsonParser = new JsonResponseParser();
3783
+ resolveModel(tier) {
3784
+ return resolveModel(this.tiers, tier, this.id);
3785
+ }
3786
+ async healthCheck() {
3787
+ return this.config.apiKey && (this.config.baseUrl || this.config.endpointUrl) ? "healthy" : "unavailable";
3788
+ }
3789
+ async estimateCost() {
3790
+ return 0;
3791
+ }
3792
+ async generate(request) {
3793
+ const model = this.modelFor(request);
3794
+ const outcome = await this.consume(this.toAdapterRequest(model, request));
3795
+ if (outcome.text.length === 0) {
3796
+ throw this.noContentError(model);
3797
+ }
3798
+ return {
3799
+ text: outcome.text,
3800
+ usage: outcome.usage,
3801
+ model,
3802
+ providerId: this.id,
3803
+ finishReason: "stop"
3804
+ };
3805
+ }
3806
+ async generateStructured(request) {
3807
+ const model = this.modelFor(request);
3808
+ const messages = this.toWireMessages(request.messages, request.systemPrompt);
3809
+ const lastUserIndex = findLastIndex4(messages, (m) => m.role === "user");
3810
+ if (lastUserIndex === -1) {
3811
+ throw new AIError(
3812
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
3813
+ "generateStructured requires at least one user message"
3814
+ );
3815
+ }
3816
+ const structuredPrompt = new StructuredPromptBuilder(
3817
+ messages[lastUserIndex]?.content ?? ""
3818
+ ).withSchema(request.schema).build();
3819
+ messages[lastUserIndex] = { role: "user", content: structuredPrompt };
3820
+ const outcome = await this.consume({
3821
+ externalModelId: model,
3822
+ messages,
3823
+ temperature: STRUCTURED_TEMPERATURE2,
3824
+ signal: request.signal
3825
+ });
3826
+ if (outcome.text.length === 0) {
3827
+ throw this.noContentError(model);
3828
+ }
3829
+ return {
3830
+ value: this.jsonParser.parse(outcome.text),
3831
+ result: {
3832
+ text: outcome.text,
3833
+ usage: outcome.usage,
3834
+ model,
3835
+ providerId: this.id,
3836
+ finishReason: "stop"
3837
+ }
3838
+ };
3839
+ }
3840
+ async stream(request, callbacks) {
3841
+ const model = this.modelFor(request);
3842
+ try {
3843
+ const outcome = await this.consume(this.toAdapterRequest(model, request), (text) => {
3844
+ callbacks.onChunk?.(text);
3845
+ });
3846
+ if (outcome.text.length === 0) {
3847
+ throw this.noContentError(model);
3848
+ }
3849
+ const result = {
3850
+ text: outcome.text,
3851
+ usage: outcome.usage,
3852
+ model,
3853
+ providerId: this.id,
3854
+ finishReason: "stop"
3855
+ };
3856
+ callbacks.onComplete?.(result);
3857
+ return result;
3858
+ } catch (error) {
3859
+ callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
3860
+ throw error;
3861
+ }
3862
+ }
3863
+ /* ---------- internals ---------- */
3864
+ /** Drain one adapter stream to its end. `onChunk`, when given, sees answer
3865
+ * deltas as they arrive — the streaming surface is this same drain. */
3866
+ async consume(req, onChunk) {
3867
+ let text = "";
3868
+ let usage;
3869
+ for await (const event of this.adapter.stream(req)) {
3870
+ if (event.type === "delta" && event.channel === "answer") {
3871
+ text += event.text;
3872
+ onChunk?.(event.text);
3873
+ } else if (event.type === "usage") {
3874
+ usage = event.usage;
3875
+ }
3876
+ }
3877
+ return { text, usage };
3878
+ }
3879
+ modelFor(request) {
3880
+ return request.model ?? this.resolveModel(request.tier ?? "balanced");
3881
+ }
3882
+ noContentError(model) {
3883
+ return new AIError(
3884
+ "SERVER_ERROR" /* SERVER_ERROR */,
3885
+ "No content generated from the OpenAI-compatible endpoint",
3886
+ void 0,
3887
+ { model }
3888
+ );
3889
+ }
3890
+ toAdapterRequest(model, request) {
3891
+ return {
3892
+ externalModelId: model,
3893
+ messages: this.toWireMessages(request.messages, request.systemPrompt),
3894
+ ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
3895
+ ...request.maxTokens !== void 0 ? { maxOutputTokens: request.maxTokens } : {},
3896
+ ...request.topP !== void 0 ? { topP: request.topP } : {},
3897
+ ...request.signal ? { signal: request.signal } : {}
3898
+ };
3899
+ }
3900
+ /** Neutral messages onto the adapter's shape; systemPrompt becomes the
3901
+ * leading system message (in-band, the OpenAI way). */
3902
+ toWireMessages(messages, systemPrompt) {
3903
+ const wire = [];
3904
+ if (systemPrompt && systemPrompt.length > 0) {
3905
+ wire.push({ role: "system", content: systemPrompt });
3906
+ }
3907
+ for (const message of messages) {
3908
+ wire.push({ role: message.role, content: message.content });
3909
+ }
3910
+ return wire;
3911
+ }
3912
+ };
3913
+ function findLastIndex4(items, predicate) {
3914
+ for (let index = items.length - 1; index >= 0; index -= 1) {
3915
+ if (predicate(items[index])) return index;
3916
+ }
3917
+ return -1;
3918
+ }
3919
+
3920
+ // src/providers/workers-ai/workers-ai.entities.ts
3921
+ var WorkersAIError = class extends Error {
3922
+ status;
3923
+ constructor(message, status) {
3924
+ super(message);
3925
+ this.name = "WorkersAIError";
3926
+ this.status = status;
3927
+ }
3928
+ };
3929
+
3930
+ // src/providers/workers-ai/workers-ai.text.ts
3931
+ var WORKERS_AI_LLM_MODEL = "@cf/meta/llama-3.3-70b-instruct-fp8-fast";
3932
+ var WORKERS_AI_TEXT_TIMEOUT_MS = 25e3;
3933
+ function requireAi(env) {
3934
+ const ai = env.AI;
3935
+ if (!ai || typeof ai.run !== "function") throw new WorkersAIError("Workers AI is not configured");
3936
+ return ai;
3937
+ }
3938
+ async function runWorkersAIText(env, messages, opts = {}) {
3939
+ const ai = requireAi(env);
3940
+ const model = opts.model ?? WORKERS_AI_LLM_MODEL;
3941
+ let result;
3942
+ try {
3943
+ result = await Promise.race([
3944
+ ai.run(model, {
3945
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
3946
+ max_tokens: opts.maxTokens ?? 900,
3947
+ temperature: opts.temperature ?? 0.7
3948
+ }),
3949
+ new Promise(
3950
+ (_, reject) => setTimeout(() => reject(new WorkersAIError("Workers AI timed out")), WORKERS_AI_TEXT_TIMEOUT_MS)
3951
+ )
3952
+ ]);
3953
+ } catch (err) {
3954
+ throw err instanceof WorkersAIError ? err : new WorkersAIError(err instanceof Error ? err.message : "Workers AI request failed");
3955
+ }
3956
+ const response = result ?? {};
3957
+ let text = response.response;
3958
+ if (typeof text !== "string" || text.length === 0) {
3959
+ text = response.choices?.[0]?.message?.content;
3960
+ }
3961
+ if (typeof text !== "string" || text.length === 0) {
3962
+ throw new WorkersAIError("Workers AI returned no text");
3963
+ }
3964
+ return text;
3965
+ }
3966
+ async function* streamWorkersAIText(env, messages, opts = {}) {
3967
+ const ai = requireAi(env);
3968
+ const model = opts.model ?? WORKERS_AI_LLM_MODEL;
3969
+ let body;
3970
+ try {
3971
+ body = await Promise.race([
3972
+ ai.run(model, {
3973
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
3974
+ stream: true,
3975
+ max_tokens: opts.maxTokens ?? 600,
3976
+ temperature: opts.temperature ?? 0.7
3977
+ }),
3978
+ new Promise(
3979
+ (_, reject) => setTimeout(() => reject(new WorkersAIError("Workers AI timed out")), WORKERS_AI_TEXT_TIMEOUT_MS)
3980
+ )
3981
+ ]);
3982
+ } catch (err) {
3983
+ throw err instanceof WorkersAIError ? err : new WorkersAIError(err instanceof Error ? err.message : "Workers AI request failed");
3984
+ }
3985
+ const stream = body;
3986
+ const getReader = typeof stream === "object" && stream !== null && "getReader" in stream && typeof stream.getReader === "function" ? stream.getReader : void 0;
3987
+ if (!getReader) throw new WorkersAIError("Workers AI stream was not readable");
3988
+ const reader = getReader.call(stream);
3989
+ const decoder = new TextDecoder();
3990
+ let buffer = "";
3991
+ try {
3992
+ for (; ; ) {
3993
+ const { done, value } = await reader.read();
3994
+ if (done) break;
3995
+ buffer += decoder.decode(value, { stream: true });
3996
+ let newlineAt;
3997
+ while ((newlineAt = buffer.indexOf("\n")) !== -1) {
3998
+ let line = buffer.slice(0, newlineAt);
3999
+ buffer = buffer.slice(newlineAt + 1);
4000
+ if (line.endsWith("\r")) line = line.slice(0, -1);
4001
+ if (line.startsWith(":") || line.trim() === "") continue;
4002
+ if (!line.startsWith("data: ")) continue;
4003
+ const payload = line.slice(6).trim();
4004
+ if (payload === "[DONE]") return;
4005
+ try {
4006
+ const parsed = JSON.parse(payload);
4007
+ if (typeof parsed.response === "string" && parsed.response.length > 0) yield parsed.response;
4008
+ } catch {
4009
+ buffer = `${line}
4010
+ ${buffer}`;
4011
+ break;
4012
+ }
4013
+ }
4014
+ }
4015
+ } finally {
4016
+ reader.releaseLock();
4017
+ }
4018
+ }
4019
+
4020
+ // src/providers/workers-ai/workers-ai.provider.ts
4021
+ var WORKERS_AI_TIERS = {
4022
+ fast: "@cf/meta/llama-3.1-8b-instruct-fp8-fast",
4023
+ balanced: WORKERS_AI_LLM_MODEL,
4024
+ powerful: WORKERS_AI_LLM_MODEL
4025
+ };
4026
+ var WorkersAIProvider = class {
4027
+ id = "workers-ai";
4028
+ name = "Cloudflare Workers AI";
4029
+ capabilities = ["text"];
4030
+ binding;
4031
+ tiers;
4032
+ jsonParser = new JsonResponseParser();
4033
+ constructor(config) {
4034
+ this.binding = config.binding;
4035
+ this.tiers = mergeTierOverrides(WORKERS_AI_TIERS, config.models);
4036
+ }
4037
+ resolveModel(tier) {
4038
+ return resolveModel(this.tiers, tier, this.id);
4039
+ }
4040
+ async healthCheck() {
4041
+ return typeof this.binding?.run === "function" ? "healthy" : "unavailable";
4042
+ }
4043
+ /** Workers AI is allocation-priced (neurons per deployment), not per
4044
+ * request — there is no per-call cost to estimate. */
4045
+ async estimateCost(_request) {
4046
+ return 0;
4047
+ }
4048
+ async generate(request) {
4049
+ const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
4050
+ const messages = withSystemPrompt(request);
4051
+ try {
4052
+ const text = await runWorkersAIText(
4053
+ { AI: this.binding },
4054
+ messages,
4055
+ {
4056
+ model,
4057
+ maxTokens: request.maxTokens,
4058
+ temperature: request.temperature
4059
+ }
4060
+ );
4061
+ return { text, model, providerId: this.id, finishReason: "stop" };
4062
+ } catch (error) {
4063
+ throw toAIError(error);
4064
+ }
4065
+ }
4066
+ async generateStructured(request) {
4067
+ const messages = [...request.messages];
4068
+ const lastUserIndex = findLastIndex5(messages, (m) => m.role === "user");
4069
+ if (lastUserIndex === -1) {
4070
+ throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "generateStructured requires at least one user message");
4071
+ }
4072
+ messages[lastUserIndex] = {
4073
+ role: "user",
4074
+ content: new StructuredPromptBuilder(messages[lastUserIndex]?.content ?? "").withSchema(request.schema).build()
4075
+ };
4076
+ const result = await this.generate({ ...request, messages, temperature: 0.1 });
4077
+ return { value: this.jsonParser.parse(result.text), result };
4078
+ }
4079
+ async stream(request, callbacks) {
4080
+ const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
4081
+ const messages = withSystemPrompt(request);
4082
+ let text = "";
4083
+ try {
4084
+ for await (const chunk of streamWorkersAIText({ AI: this.binding }, messages, {
4085
+ model,
4086
+ maxTokens: request.maxTokens,
4087
+ temperature: request.temperature
4088
+ })) {
4089
+ text += chunk;
4090
+ callbacks.onChunk?.(chunk);
4091
+ }
4092
+ } catch (error) {
4093
+ const aiError = toAIError(error);
4094
+ callbacks.onError?.(aiError);
4095
+ throw aiError;
4096
+ }
4097
+ const result = { text, model, providerId: this.id, finishReason: "stop" };
4098
+ callbacks.onComplete?.(result);
4099
+ return result;
4100
+ }
4101
+ };
4102
+ function withSystemPrompt(request) {
4103
+ if (request.systemPrompt && request.systemPrompt.length > 0) {
4104
+ return [
4105
+ { role: "system", content: request.systemPrompt },
4106
+ ...request.messages.filter((m) => m.role !== "system")
4107
+ ];
4108
+ }
4109
+ return request.messages;
4110
+ }
4111
+ function toAIError(error) {
4112
+ if (error instanceof AIError) return error;
4113
+ const message = error instanceof Error ? error.message : "Workers AI request failed";
4114
+ const type = error instanceof WorkersAIError && error.message.includes("not configured") ? "INVALID_API_KEY" /* INVALID_API_KEY */ : "SERVER_ERROR" /* SERVER_ERROR */;
4115
+ return new AIError(type, message, error instanceof Error ? error : void 0);
4116
+ }
4117
+ function findLastIndex5(items, predicate) {
4118
+ for (let i = items.length - 1; i >= 0; i--) {
4119
+ const item = items[i];
4120
+ if (item !== void 0 && predicate(item)) return i;
4121
+ }
4122
+ return -1;
4123
+ }
4124
+
3520
4125
  // src/providers/factory/create-ai.ts
3521
4126
  function createAI(config = {}) {
3522
4127
  const factory = new ProviderFactory({
@@ -3539,6 +4144,15 @@ function createAI(config = {}) {
3539
4144
  if (config.pruna?.apiKey) {
3540
4145
  factory.register(new PrunaProvider({ ...config.pruna, enabled: true }));
3541
4146
  }
4147
+ const openaiCompatible = config.openaiCompatible;
4148
+ if (openaiCompatible?.apiKey && (openaiCompatible.baseUrl || openaiCompatible.endpointUrl)) {
4149
+ factory.register(
4150
+ new OpenAiCompatibleProvider({ ...openaiCompatible, apiKey: openaiCompatible.apiKey })
4151
+ );
4152
+ }
4153
+ if (config.workersAI?.binding) {
4154
+ factory.register(new WorkersAIProvider(config.workersAI));
4155
+ }
3542
4156
  return factory;
3543
4157
  }
3544
4158
 
@@ -3995,7 +4609,7 @@ async function batchRequest(requests, options = {}) {
3995
4609
  errors.push(result.reason);
3996
4610
  if (stopOnError) {
3997
4611
  throw new Error(
3998
- `Batch request failed: ${errors[0].message}`
4612
+ `Batch request failed: ${errors[0]?.message}`
3999
4613
  );
4000
4614
  }
4001
4615
  }
@@ -4560,7 +5174,7 @@ function safeJSONParse(text, fallback, onError) {
4560
5174
  }
4561
5175
  if (error instanceof SyntaxError) {
4562
5176
  const positionMatch = error.message.match(/position (\d+)/);
4563
- const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
5177
+ const position = positionMatch ? parseInt(positionMatch[1] ?? "", 10) : -1;
4564
5178
  if (position > 0) {
4565
5179
  text.substring(Math.max(0, position - 50), position + 50);
4566
5180
  }
@@ -5228,7 +5842,9 @@ function useAIGeneration(options) {
5228
5842
  setProgress({ stage: "uploading", progress: 0, message: "Uploading files..." });
5229
5843
  const urls = [];
5230
5844
  for (let i = 0; i < files.length; i++) {
5231
- const url = await uploadPhoto(files[i], userId);
5845
+ const file = files[i];
5846
+ if (!file) continue;
5847
+ const url = await uploadPhoto(file, userId);
5232
5848
  urls.push(url);
5233
5849
  setProgress({
5234
5850
  stage: "uploading",
@@ -6032,7 +6648,7 @@ function useChat(options, deps) {
6032
6648
  if (streamEnabled) {
6033
6649
  const placeholderId = chatIdGenerator.next("assistant");
6034
6650
  streamingPlaceholderId = placeholderId;
6035
- const placeholder = {
6651
+ const placeholder2 = {
6036
6652
  id: placeholderId,
6037
6653
  sender: "assistant",
6038
6654
  content: "",
@@ -6040,7 +6656,7 @@ function useChat(options, deps) {
6040
6656
  type: "text",
6041
6657
  metadata: { streaming: true }
6042
6658
  };
6043
- setMessages((prev) => [...prev, placeholder]);
6659
+ setMessages((prev) => [...prev, placeholder2]);
6044
6660
  const fullText = await chatService.generateStreamingResponse(
6045
6661
  trimmed,
6046
6662
  contextForAI,
@@ -6054,9 +6670,9 @@ function useChat(options, deps) {
6054
6670
  controller.signal
6055
6671
  );
6056
6672
  const finalMessage = {
6057
- ...placeholder,
6673
+ ...placeholder2,
6058
6674
  content: fullText,
6059
- metadata: { ...placeholder.metadata, streaming: false }
6675
+ metadata: { ...placeholder2.metadata, streaming: false }
6060
6676
  };
6061
6677
  setMessages(
6062
6678
  (prev) => prev.map((m) => m.id === placeholderId ? finalMessage : m)
@@ -6104,7 +6720,7 @@ function useChat(options, deps) {
6104
6720
  if (currentMessages.length === 0) return null;
6105
6721
  const lastUserMessage = [...currentMessages].reverse().find((m) => m.sender === "user");
6106
6722
  if (!lastUserMessage) return null;
6107
- const trimmed = currentMessages.length > 0 && currentMessages[currentMessages.length - 1].sender === "assistant" ? currentMessages.slice(0, -1) : currentMessages;
6723
+ const trimmed = currentMessages.length > 0 && currentMessages[currentMessages.length - 1]?.sender === "assistant" ? currentMessages.slice(0, -1) : currentMessages;
6108
6724
  const controller = new AbortController();
6109
6725
  abortControllerRef.current?.abort();
6110
6726
  abortControllerRef.current = controller;
@@ -6119,7 +6735,7 @@ function useChat(options, deps) {
6119
6735
  controller.signal
6120
6736
  );
6121
6737
  setMessages((prev) => {
6122
- const lastIsAI = prev.length > 0 && prev[prev.length - 1].sender === "assistant";
6738
+ const lastIsAI = prev.length > 0 && prev[prev.length - 1]?.sender === "assistant";
6123
6739
  const base = lastIsAI ? prev.slice(0, -1) : prev;
6124
6740
  return [...base, aiResponse];
6125
6741
  });
@@ -6321,6 +6937,120 @@ function useGroq(options = {}, deps) {
6321
6937
  clearError
6322
6938
  };
6323
6939
  }
6940
+ var REMOTE_CHAT_STREAMING_ID = "streaming";
6941
+ function placeholder(id, status) {
6942
+ return { id, role: "assistant", content: "", reasoning: "", status, error: null, usage: null };
6943
+ }
6944
+ function useRemoteChat(options) {
6945
+ const { client } = options;
6946
+ const [messages, setMessages] = useState([]);
6947
+ const [isStreaming, setIsStreaming] = useState(false);
6948
+ const [isResolving, setIsResolving] = useState(false);
6949
+ const handleRef = useRef(null);
6950
+ const stoppedRef = useRef(false);
6951
+ const optionsRef = useRef(options);
6952
+ optionsRef.current = options;
6953
+ const patchLast = useCallback((patch) => {
6954
+ setMessages((prev) => {
6955
+ if (prev.length === 0) return prev;
6956
+ const next = [...prev];
6957
+ const last = next[next.length - 1];
6958
+ if (!last) return prev;
6959
+ next[next.length - 1] = { ...last, ...patch };
6960
+ return next;
6961
+ });
6962
+ }, []);
6963
+ const appendDelta = useCallback((channel, text) => {
6964
+ setMessages((prev) => {
6965
+ if (prev.length === 0) return prev;
6966
+ const next = [...prev];
6967
+ const last = next[next.length - 1];
6968
+ if (!last) return prev;
6969
+ next[next.length - 1] = channel === "reasoning" ? { ...last, reasoning: last.reasoning + text } : { ...last, content: last.content + text };
6970
+ return next;
6971
+ });
6972
+ }, []);
6973
+ const callbacks = useCallback(
6974
+ () => ({
6975
+ onDelta: appendDelta,
6976
+ onUsage: (usage) => patchLast({ usage }),
6977
+ onError: (code, message) => patchLast({ status: "error", error: { code, message } }),
6978
+ onDone: (messageId) => patchLast({ id: messageId, status: "complete" }),
6979
+ onToolCall: (toolCall) => optionsRef.current.onToolCall?.(toolCall)
6980
+ }),
6981
+ [appendDelta, patchLast]
6982
+ );
6983
+ const stream = useCallback(
6984
+ async (conversationId, body, opts) => {
6985
+ setIsStreaming(true);
6986
+ stoppedRef.current = false;
6987
+ setMessages(() => [
6988
+ ...opts?.localUserContent !== void 0 ? [
6989
+ {
6990
+ id: optionsRef.current.localUserId?.() ?? `local-${Date.now()}`,
6991
+ role: "user",
6992
+ content: opts.localUserContent,
6993
+ reasoning: "",
6994
+ status: "complete",
6995
+ error: null,
6996
+ usage: null
6997
+ }
6998
+ ] : [],
6999
+ placeholder(REMOTE_CHAT_STREAMING_ID, "streaming")
7000
+ ]);
7001
+ const handle = client.streamChat(conversationId, body, callbacks());
7002
+ handleRef.current = handle;
7003
+ try {
7004
+ await handle.promise.catch(() => void 0);
7005
+ } finally {
7006
+ handleRef.current = null;
7007
+ setIsStreaming(false);
7008
+ await optionsRef.current.onStreamEnd?.({
7009
+ conversationId,
7010
+ stopped: stoppedRef.current
7011
+ });
7012
+ stoppedRef.current = false;
7013
+ }
7014
+ },
7015
+ [callbacks, client]
7016
+ );
7017
+ const resolveToolCall = useCallback(
7018
+ async (conversationId, toolCallId, approved) => {
7019
+ setIsResolving(true);
7020
+ setIsStreaming(true);
7021
+ stoppedRef.current = false;
7022
+ setMessages((prev) => [...prev, placeholder(REMOTE_CHAT_STREAMING_ID, "streaming")]);
7023
+ try {
7024
+ return await client.resolveToolCall(conversationId, toolCallId, approved, callbacks());
7025
+ } catch {
7026
+ return false;
7027
+ } finally {
7028
+ setIsStreaming(false);
7029
+ setIsResolving(false);
7030
+ await optionsRef.current.onStreamEnd?.({
7031
+ conversationId,
7032
+ stopped: stoppedRef.current
7033
+ });
7034
+ stoppedRef.current = false;
7035
+ }
7036
+ },
7037
+ [callbacks, client]
7038
+ );
7039
+ const abort = useCallback(() => {
7040
+ stoppedRef.current = true;
7041
+ handleRef.current?.abort();
7042
+ }, []);
7043
+ return {
7044
+ messages,
7045
+ setMessages,
7046
+ isStreaming,
7047
+ isResolving,
7048
+ stream,
7049
+ resolveToolCall,
7050
+ abort,
7051
+ patchLast
7052
+ };
7053
+ }
6324
7054
  function isAbortError2(err) {
6325
7055
  if (!(err instanceof Error)) return false;
6326
7056
  if (err.name === "AbortError") return true;
@@ -6434,6 +7164,6 @@ function usePrunaProxy(options) {
6434
7164
  );
6435
7165
  }
6436
7166
 
6437
- export { CancellationError, CREDIT_COSTS as GENERATION_COSTS, calculateContentCreditCost as calculateCreditCost, useAIContent, useAIGeneration, useAsyncAction, useBlogGenerator, useCacheStats, useChat, useContentWizard, useGroq, useImageWizard, usePrunaGeneration, usePrunaProxy, useSocialContentGenerator, useText, useVideoWizard };
7167
+ export { CancellationError, CREDIT_COSTS as GENERATION_COSTS, REMOTE_CHAT_STREAMING_ID, calculateContentCreditCost as calculateCreditCost, useAIContent, useAIGeneration, useAsyncAction, useBlogGenerator, useCacheStats, useChat, useContentWizard, useGroq, useImageWizard, usePrunaGeneration, usePrunaProxy, useRemoteChat, useSocialContentGenerator, useText, useVideoWizard };
6438
7168
  //# sourceMappingURL=index.js.map
6439
7169
  //# sourceMappingURL=index.js.map