@absolutejs/ai 0.0.49 → 0.0.51

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 (39) hide show
  1. package/README.md +127 -0
  2. package/dist/ai/client/index.js +30 -1
  3. package/dist/ai/client/index.js.map +5 -5
  4. package/dist/ai/index.js +1174 -92
  5. package/dist/ai/index.js.map +18 -16
  6. package/dist/ai/providers/anthropic.js +105 -26
  7. package/dist/ai/providers/anthropic.js.map +5 -5
  8. package/dist/ai/providers/gemini.js +37 -4
  9. package/dist/ai/providers/gemini.js.map +5 -5
  10. package/dist/ai/providers/ollama.js +7 -3
  11. package/dist/ai/providers/ollama.js.map +4 -4
  12. package/dist/ai/providers/openai.js +177 -36
  13. package/dist/ai/providers/openai.js.map +5 -5
  14. package/dist/ai/providers/openaiCompatible.js +177 -36
  15. package/dist/ai/providers/openaiCompatible.js.map +5 -5
  16. package/dist/ai/providers/openaiResponses.js +147 -33
  17. package/dist/ai/providers/openaiResponses.js.map +5 -5
  18. package/dist/ai/providers/openrouter.js +2607 -0
  19. package/dist/ai/providers/openrouter.js.map +18 -0
  20. package/dist/angular/ai/index.js +30 -1
  21. package/dist/angular/ai/index.js.map +5 -5
  22. package/dist/react/ai/index.js +30 -1
  23. package/dist/react/ai/index.js.map +5 -5
  24. package/dist/src/ai/client/actions.d.ts +53 -0
  25. package/dist/src/ai/errors/providerError.d.ts +3 -0
  26. package/dist/src/ai/generateAI.d.ts +4 -1
  27. package/dist/src/ai/index.d.ts +2 -0
  28. package/dist/src/ai/providers/openai.d.ts +7 -3
  29. package/dist/src/ai/providers/openaiResponses.d.ts +9 -4
  30. package/dist/src/ai/providers/openrouter.d.ts +157 -0
  31. package/dist/src/ai/providers/openrouterClient.d.ts +276 -0
  32. package/dist/src/ai/streamAIWithTools.d.ts +6 -0
  33. package/dist/svelte/ai/index.js +30 -1
  34. package/dist/svelte/ai/index.js.map +5 -5
  35. package/dist/types/ai.d.ts +116 -8
  36. package/dist/types/anthropic.d.ts +13 -5
  37. package/dist/vue/ai/index.js +30 -1
  38. package/dist/vue/ai/index.js.map +5 -5
  39. package/package.json +9 -2
package/dist/ai/index.js CHANGED
@@ -6,7 +6,8 @@ var PROVIDER_STATUS_PAGES = {
6
6
  anthropic: "https://status.claude.com",
7
7
  gemini: "https://status.cloud.google.com",
8
8
  google: "https://status.cloud.google.com",
9
- openai: "https://status.openai.com"
9
+ openai: "https://status.openai.com",
10
+ openrouter: "https://status.openrouter.ai"
10
11
  };
11
12
  var RETRYABLE_STATUSES = new Set([
12
13
  408,
@@ -42,6 +43,7 @@ class ProviderError extends Error {
42
43
  status;
43
44
  type;
44
45
  retryable;
46
+ metadata;
45
47
  statusPageUrl;
46
48
  constructor(init) {
47
49
  super(init.message, init.cause === undefined ? undefined : { cause: init.cause });
@@ -50,6 +52,7 @@ class ProviderError extends Error {
50
52
  this.status = init.status ?? null;
51
53
  this.type = init.type ?? null;
52
54
  this.retryable = init.retryable;
55
+ this.metadata = init.metadata;
53
56
  this.statusPageUrl = providerStatusPage(init.provider);
54
57
  }
55
58
  static fromResponse(provider, status, body, type) {
@@ -222,7 +225,8 @@ var withResilience = (provider, providerName = "unknown") => {
222
225
  yield* attempt(params, attemptNo + 1);
223
226
  return;
224
227
  }
225
- noteFailure(providerName, providerError);
228
+ if (providerError.retryable)
229
+ noteFailure(providerName, providerError);
226
230
  throw providerError;
227
231
  }
228
232
  };
@@ -456,7 +460,7 @@ var mapContentBlockToOpenAI = (block) => {
456
460
  if (block.type === "image") {
457
461
  return {
458
462
  image_url: {
459
- url: `data:${block.source.media_type};base64,${block.source.data}`
463
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
460
464
  },
461
465
  type: "image_url"
462
466
  };
@@ -464,12 +468,26 @@ var mapContentBlockToOpenAI = (block) => {
464
468
  if (block.type === "document") {
465
469
  return {
466
470
  file: {
467
- file_data: `data:${block.source.media_type};base64,${block.source.data}`,
471
+ file_data: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`,
468
472
  filename: block.name ?? "document.pdf"
469
473
  },
470
474
  type: "file"
471
475
  };
472
476
  }
477
+ if (block.type === "audio") {
478
+ return {
479
+ input_audio: { data: block.source.data, format: block.source.format },
480
+ type: "input_audio"
481
+ };
482
+ }
483
+ if (block.type === "video") {
484
+ return {
485
+ type: "video_url",
486
+ video_url: {
487
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
488
+ }
489
+ };
490
+ }
473
491
  if (block.type === "text") {
474
492
  return { text: block.content, type: "text" };
475
493
  }
@@ -479,17 +497,20 @@ var mapOpenAIContent = (msg) => {
479
497
  if (typeof msg.content === "string") {
480
498
  return msg.content;
481
499
  }
482
- const hasMedia = msg.content.some((block) => block.type === "image" || block.type === "document");
500
+ const hasMedia = msg.content.some((block) => block.type === "image" || block.type === "document" || block.type === "audio" || block.type === "video");
483
501
  if (!hasMedia) {
484
502
  return null;
485
503
  }
486
504
  return msg.content.map(mapContentBlockToOpenAI).filter((mapped) => mapped !== null);
487
505
  };
488
- var buildRequestBody = (params) => {
506
+ var buildRequestBody = (params, capabilityModel = params.model) => {
489
507
  const messages = convertToolResultMessages(params.messages.map((msg) => ({
490
508
  content: mapOpenAIContent(msg),
491
509
  role: msg.role
492
510
  })), params);
511
+ if (params.systemPrompt) {
512
+ messages.unshift({ content: params.systemPrompt, role: "system" });
513
+ }
493
514
  const body = {
494
515
  messages,
495
516
  model: params.model,
@@ -510,12 +531,12 @@ var buildRequestBody = (params) => {
510
531
  body.parallel_tool_calls = params.parallelToolCalls;
511
532
  }
512
533
  }
513
- if (isOpenAIReasoningModel(params.model)) {
534
+ if (isOpenAIReasoningModel(capabilityModel)) {
514
535
  if (typeof params.maxTokens === "number") {
515
536
  body.max_completion_tokens = params.maxTokens;
516
537
  }
517
538
  if (params.reasoning) {
518
- const effort = openaiEffortValue(params.model, params.reasoning);
539
+ const effort = openaiEffortValue(capabilityModel, params.reasoning);
519
540
  if (effort)
520
541
  body.reasoning_effort = effort;
521
542
  }
@@ -576,8 +597,12 @@ var extractUsage = (parsedUsage) => {
576
597
  const cached = parsedUsage.cached_tokens ?? 0;
577
598
  return {
578
599
  cacheReadInputTokens: cached,
600
+ cacheWriteInputTokens: parsedUsage.cache_write_tokens || undefined,
601
+ costCredits: parsedUsage.cost,
579
602
  inputTokens: Math.max(0, prompt - cached),
580
- outputTokens: parsedUsage.completion_tokens ?? 0
603
+ outputTokens: parsedUsage.completion_tokens ?? 0,
604
+ reasoningTokens: parsedUsage.reasoning_tokens || undefined,
605
+ upstreamInferenceCostCredits: parsedUsage.upstream_inference_cost || undefined
581
606
  };
582
607
  };
583
608
  var resolveToolCallIndex = (toolCall) => {
@@ -629,9 +654,50 @@ var processDelta = function* (delta, pendingToolCalls) {
629
654
  if (typeof delta.content === "string") {
630
655
  yield { content: delta.content, type: "text" };
631
656
  }
657
+ if (isRecord(delta.audio)) {
658
+ const audio = delta.audio;
659
+ if (typeof audio.data === "string" || typeof audio.transcript === "string") {
660
+ yield {
661
+ audioId: typeof audio.id === "string" ? audio.id : undefined,
662
+ data: typeof audio.data === "string" ? audio.data : "",
663
+ format: typeof audio.format === "string" ? audio.format : "pcm16",
664
+ transcript: typeof audio.transcript === "string" ? audio.transcript : undefined,
665
+ type: "audio"
666
+ };
667
+ }
668
+ }
632
669
  if (isRecordArray(delta.tool_calls)) {
633
670
  processToolCallDeltas(delta.tool_calls, pendingToolCalls);
634
671
  }
672
+ if (Array.isArray(delta.annotations)) {
673
+ for (const annotation of delta.annotations) {
674
+ if (!isRecord(annotation) || annotation.type !== "url_citation")
675
+ continue;
676
+ const citation = isRecord(annotation.url_citation) ? annotation.url_citation : annotation;
677
+ if (typeof citation.url !== "string")
678
+ continue;
679
+ yield {
680
+ content: typeof citation.content === "string" ? citation.content : undefined,
681
+ endIndex: typeof citation.end_index === "number" ? citation.end_index : undefined,
682
+ startIndex: typeof citation.start_index === "number" ? citation.start_index : undefined,
683
+ title: typeof citation.title === "string" ? citation.title : undefined,
684
+ type: "citation",
685
+ url: citation.url
686
+ };
687
+ }
688
+ }
689
+ };
690
+ var narrowResponseMetadata = (parsed) => {
691
+ const providerMetadata = isRecord(parsed.openrouter_metadata) ? parsed.openrouter_metadata : undefined;
692
+ const generationId = typeof parsed.id === "string" ? parsed.id : undefined;
693
+ const model = typeof parsed.model === "string" ? parsed.model : undefined;
694
+ const serviceTier = typeof parsed.service_tier === "string" ? parsed.service_tier : undefined;
695
+ const selected = providerMetadata && isRecord(providerMetadata.endpoints) ? providerMetadata.endpoints.available : undefined;
696
+ const selectedEndpoint = Array.isArray(selected) ? selected.find((entry) => isRecord(entry) && entry.selected === true) : undefined;
697
+ const provider = isRecord(selectedEndpoint) && typeof selectedEndpoint.provider === "string" ? selectedEndpoint.provider : undefined;
698
+ if (!providerMetadata && !generationId && !model && !serviceTier)
699
+ return;
700
+ return { generationId, model, provider, providerMetadata, serviceTier };
635
701
  };
636
702
  var processChoice = function* (choice, pendingToolCalls) {
637
703
  const delta = isRecord(choice.delta) ? choice.delta : null;
@@ -650,13 +716,25 @@ var narrowUsageRecord = (parsed) => {
650
716
  const promptTokens = typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0;
651
717
  const completionTokens = typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0;
652
718
  const cachedTokens = isRecord(usage.prompt_tokens_details) && typeof usage.prompt_tokens_details.cached_tokens === "number" ? usage.prompt_tokens_details.cached_tokens : 0;
653
- return extractUsage({
719
+ const cacheWriteTokens = isRecord(usage.prompt_tokens_details) && typeof usage.prompt_tokens_details.cache_write_tokens === "number" ? usage.prompt_tokens_details.cache_write_tokens : 0;
720
+ const reasoningTokens = isRecord(usage.completion_tokens_details) && typeof usage.completion_tokens_details.reasoning_tokens === "number" ? usage.completion_tokens_details.reasoning_tokens : 0;
721
+ const cost = typeof usage.cost === "number" ? usage.cost : undefined;
722
+ const upstreamInferenceCost = isRecord(usage.cost_details) && typeof usage.cost_details.upstream_inference_cost === "number" ? usage.cost_details.upstream_inference_cost : undefined;
723
+ const normalized = extractUsage({
724
+ cache_write_tokens: cacheWriteTokens,
654
725
  cached_tokens: cachedTokens,
655
726
  completion_tokens: completionTokens,
656
- prompt_tokens: promptTokens
727
+ cost,
728
+ prompt_tokens: promptTokens,
729
+ reasoning_tokens: reasoningTokens,
730
+ upstream_inference_cost: upstreamInferenceCost
657
731
  });
732
+ if (isRecord(usage.server_tool_use)) {
733
+ normalized.serverToolUse = Object.fromEntries(Object.entries(usage.server_tool_use).filter((entry) => typeof entry[1] === "number"));
734
+ }
735
+ return normalized;
658
736
  };
659
- var processSSELine = function* (line, pendingToolCalls, currentUsage) {
737
+ var processSSELine = function* (line, pendingToolCalls, providerName) {
660
738
  const trimmed = line.trim();
661
739
  if (!trimmed || !trimmed.startsWith("data: ")) {
662
740
  return;
@@ -664,7 +742,6 @@ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
664
742
  const data = trimmed.slice(SSE_DATA_PREFIX_LENGTH);
665
743
  if (data === DONE_SENTINEL) {
666
744
  yield* flushPendingToolCalls(pendingToolCalls);
667
- yield { type: "done", usage: currentUsage };
668
745
  return;
669
746
  }
670
747
  let parsed;
@@ -673,10 +750,28 @@ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
673
750
  } catch {
674
751
  return;
675
752
  }
753
+ if (isRecord(parsed.error)) {
754
+ const error = parsed.error;
755
+ const metadata2 = isRecord(error.metadata) ? error.metadata : undefined;
756
+ const status = typeof error.code === "number" ? error.code : null;
757
+ const type = metadata2 && typeof metadata2.error_type === "string" ? metadata2.error_type : typeof error.error_type === "string" ? error.error_type : null;
758
+ throw new ProviderError({
759
+ message: typeof error.message === "string" ? error.message : "OpenRouter stream failed",
760
+ metadata: metadata2,
761
+ provider: providerName,
762
+ retryable: status === 408 || status === 409 || status === 425 || status === 429 || status !== null && status >= 500,
763
+ status,
764
+ type
765
+ });
766
+ }
676
767
  const usageUpdate = narrowUsageRecord(parsed);
677
768
  if (usageUpdate) {
678
769
  yield { type: "usage_update", usage: usageUpdate };
679
770
  }
771
+ const metadata = narrowResponseMetadata(parsed);
772
+ if (metadata) {
773
+ yield { metadata, type: "response_metadata" };
774
+ }
680
775
  const { choices } = parsed;
681
776
  if (!isRecordArray(choices)) {
682
777
  return;
@@ -685,21 +780,46 @@ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
685
780
  if (!firstChoice) {
686
781
  return;
687
782
  }
783
+ if (isRecord(firstChoice.error)) {
784
+ const error = firstChoice.error;
785
+ const metadata2 = isRecord(error.metadata) ? error.metadata : undefined;
786
+ const status = typeof error.code === "number" ? error.code : null;
787
+ throw new ProviderError({
788
+ message: typeof error.message === "string" ? error.message : "OpenRouter stream failed",
789
+ metadata: metadata2,
790
+ provider: providerName,
791
+ retryable: status === 429 || status !== null && status >= 500,
792
+ status,
793
+ type: metadata2 && typeof metadata2.error_type === "string" ? metadata2.error_type : null
794
+ });
795
+ }
688
796
  yield* processChoice(firstChoice, pendingToolCalls);
689
797
  };
690
798
  var isUsageUpdate = (chunk) => chunk.type === "usage_update";
691
- var collectYieldableChunks = (line, pendingToolCalls, usageRef) => {
692
- const allChunks = Array.from(processSSELine(line, pendingToolCalls, usageRef.current));
799
+ var collectYieldableChunks = (line, pendingToolCalls, usageRef, metadataRef, providerName) => {
800
+ const allChunks = Array.from(processSSELine(line, pendingToolCalls, providerName));
693
801
  const usageChunks = allChunks.filter(isUsageUpdate);
694
802
  const lastUsage = usageChunks.at(NOT_FOUND);
695
803
  if (lastUsage) {
696
804
  usageRef.current = lastUsage.usage;
697
805
  }
698
- return allChunks.filter((chunk) => !isUsageUpdate(chunk));
806
+ const metadataChunks = allChunks.filter((chunk) => chunk.type === "response_metadata");
807
+ const lastMetadata = metadataChunks.at(NOT_FOUND);
808
+ if (lastMetadata && "metadata" in lastMetadata) {
809
+ metadataRef.current = {
810
+ ...metadataRef.current,
811
+ ...lastMetadata.metadata,
812
+ providerMetadata: {
813
+ ...metadataRef.current?.providerMetadata,
814
+ ...lastMetadata.metadata.providerMetadata
815
+ }
816
+ };
817
+ }
818
+ return allChunks.filter((chunk) => !isUsageUpdate(chunk) && chunk.type !== "response_metadata");
699
819
  };
700
- var processSSELines = function* (lines, pendingToolCalls, usageRef) {
820
+ var processSSELines = function* (lines, pendingToolCalls, usageRef, metadataRef, providerName) {
701
821
  for (const line of lines) {
702
- yield* collectYieldableChunks(line, pendingToolCalls, usageRef);
822
+ yield* collectYieldableChunks(line, pendingToolCalls, usageRef, metadataRef, providerName);
703
823
  }
704
824
  };
705
825
  var processStreamValue = (value, decoder, state) => {
@@ -712,51 +832,67 @@ var processStreamValue = (value, decoder, state) => {
712
832
  var drainReader = async function* (reader, decoder, state, signal) {
713
833
  for (let result = await reader.read();!result.done && !signal?.aborted; result = await reader.read()) {
714
834
  const lines = processStreamValue(result.value, decoder, state);
715
- yield* processSSELines(lines, state.pendingToolCalls, state.usageRef);
835
+ yield* processSSELines(lines, state.pendingToolCalls, state.usageRef, state.metadataRef, state.providerName);
716
836
  }
717
837
  };
718
- var parseSSEStream = async function* (body, signal) {
838
+ var parseSSEStream = async function* (body, initialMetadata, providerName = "openai", signal) {
719
839
  const reader = body.getReader();
720
840
  const decoder = new TextDecoder;
721
841
  const state = {
722
842
  buffer: "",
843
+ metadataRef: { current: initialMetadata },
723
844
  pendingToolCalls: new Map,
845
+ providerName,
724
846
  usageRef: { current: undefined }
725
847
  };
726
848
  try {
727
849
  yield* drainReader(reader, decoder, state, signal);
728
- yield { type: "done", usage: state.usageRef.current };
850
+ yield {
851
+ metadata: state.metadataRef.current,
852
+ type: "done",
853
+ usage: state.usageRef.current
854
+ };
729
855
  } finally {
730
856
  reader.releaseLock();
731
857
  }
732
858
  };
733
- var fetchOpenAIStream = async function* (baseUrl, apiKey, body, signal) {
859
+ var fetchOpenAIStream = async function* (baseUrl, apiKey, body, fetchImpl, headers, providerName, signal) {
734
860
  const target = `${baseUrl}/v1/chat/completions`;
735
- const response = await fetch(target, {
861
+ const requestHeaders = new Headers(headers);
862
+ requestHeaders.set("Authorization", `Bearer ${apiKey}`);
863
+ requestHeaders.set("Content-Type", "application/json");
864
+ const response = await fetchImpl(target, {
736
865
  ...h2IfHttps(target),
737
866
  body: JSON.stringify(body),
738
- headers: {
739
- Authorization: `Bearer ${apiKey}`,
740
- "Content-Type": "application/json"
741
- },
867
+ headers: requestHeaders,
742
868
  method: "POST",
743
869
  signal
744
870
  });
745
871
  if (!response.ok) {
746
872
  const errorText = await response.text();
747
- throw ProviderError.fromResponse("openai", response.status, errorText);
873
+ throw ProviderError.fromResponse(providerName, response.status, errorText);
748
874
  }
749
875
  if (!response.body) {
750
876
  throw new ProviderError({
751
- message: "OpenAI API returned no response body",
752
- provider: "openai",
877
+ message: `${providerName} API returned no response body`,
878
+ provider: providerName,
753
879
  retryable: true
754
880
  });
755
881
  }
756
- yield* parseSSEStream(response.body, signal);
882
+ yield* parseSSEStream(response.body, {
883
+ generationId: response.headers.get("X-Generation-Id") ?? undefined,
884
+ providerMetadata: {
885
+ cacheAge: response.headers.get("X-OpenRouter-Cache-Age") ?? undefined,
886
+ cacheSourceId: response.headers.get("X-OpenRouter-Cache-Source-Id") ?? undefined,
887
+ cacheStatus: response.headers.get("X-OpenRouter-Cache-Status") ?? undefined,
888
+ cacheTtl: response.headers.get("X-OpenRouter-Cache-TTL") ?? undefined
889
+ }
890
+ }, providerName, signal);
757
891
  };
758
892
  var openai = (config2) => {
759
893
  const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL;
894
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
895
+ const providerName = config2.providerName ?? "openai";
760
896
  if (!config2.apiKey && !config2.tokenSource) {
761
897
  throw new Error("openai() requires either apiKey or tokenSource");
762
898
  }
@@ -766,15 +902,20 @@ var openai = (config2) => {
766
902
  }
767
903
  return config2.apiKey;
768
904
  };
905
+ const resolveHeaders = async (params) => typeof config2.headers === "function" ? await config2.headers(params) : config2.headers ?? {};
769
906
  return instrumentAIProvider({
770
907
  stream: (params) => {
771
- const body = buildRequestBody(params);
908
+ const openaiBody = buildRequestBody(params, config2.modelForCapabilities?.(params.model) ?? params.model);
909
+ const body = config2.transformRequestBody ? config2.transformRequestBody(openaiBody, params) : openaiBody;
772
910
  return async function* () {
773
- const apiKey = await resolveKey();
774
- yield* fetchOpenAIStream(baseUrl, apiKey, body, params.signal);
911
+ const [apiKey, headers] = await Promise.all([
912
+ resolveKey(),
913
+ resolveHeaders(params)
914
+ ]);
915
+ yield* fetchOpenAIStream(baseUrl, apiKey, body, fetchImpl, headers, providerName, params.signal);
775
916
  }();
776
917
  }
777
- }, "openai");
918
+ }, providerName);
778
919
  };
779
920
 
780
921
  // src/ai/providers/openaiCompatible.ts
@@ -826,7 +967,7 @@ var mapBlockToResponsesFormat = (block) => {
826
967
  if (block.type === "image") {
827
968
  return {
828
969
  image_url: {
829
- url: `data:${block.source.media_type};base64,${block.source.data}`
970
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
830
971
  },
831
972
  type: "input_image"
832
973
  };
@@ -834,12 +975,24 @@ var mapBlockToResponsesFormat = (block) => {
834
975
  if (block.type === "document") {
835
976
  return {
836
977
  file: {
837
- file_data: `data:${block.source.media_type};base64,${block.source.data}`,
978
+ file_data: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`,
838
979
  filename: block.name ?? "document.pdf"
839
980
  },
840
981
  type: "input_file"
841
982
  };
842
983
  }
984
+ if (block.type === "audio") {
985
+ return {
986
+ input_audio: { data: block.source.data, format: block.source.format },
987
+ type: "input_audio"
988
+ };
989
+ }
990
+ if (block.type === "video") {
991
+ return {
992
+ type: "input_video",
993
+ video_url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
994
+ };
995
+ }
843
996
  return null;
844
997
  };
845
998
  var mapContentToResponsesFormat = (content) => {
@@ -851,7 +1004,12 @@ var mapContentToResponsesFormat = (content) => {
851
1004
  };
852
1005
  var hasToolBlocks = (content) => content.some((block) => block.type === "tool_use" || block.type === "tool_result");
853
1006
  var convertToolBlock = (block) => {
1007
+ if (block.type === "provider_data" && block.provider === "openrouter") {
1008
+ return { ...block.data };
1009
+ }
854
1010
  if (block.type === "tool_use") {
1011
+ if (block.providerData)
1012
+ return { ...block.providerData };
855
1013
  return {
856
1014
  arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input),
857
1015
  call_id: block.id,
@@ -903,7 +1061,7 @@ var buildTools = (tools, isImageModel) => {
903
1061
  }
904
1062
  return result.length > 0 ? result : undefined;
905
1063
  };
906
- var buildRequestBody2 = (params, isImageModel) => {
1064
+ var buildRequestBody2 = (params, isImageModel, capabilityModel = params.model) => {
907
1065
  const body = {
908
1066
  input: buildInput(params.messages),
909
1067
  model: params.model,
@@ -955,8 +1113,8 @@ var buildRequestBody2 = (params, isImageModel) => {
955
1113
  };
956
1114
  }
957
1115
  }
958
- if (params.reasoning && isOpenAIReasoningModel(params.model)) {
959
- const effort = openaiEffortValue(params.model, params.reasoning);
1116
+ if (params.reasoning && isOpenAIReasoningModel(capabilityModel)) {
1117
+ const effort = openaiEffortValue(capabilityModel, params.reasoning);
960
1118
  if (effort) {
961
1119
  body.reasoning = {
962
1120
  effort,
@@ -987,11 +1145,32 @@ var extractUsage2 = (response) => {
987
1145
  const { usage } = response;
988
1146
  const input = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
989
1147
  const cached = isRecord2(usage.input_tokens_details) && typeof usage.input_tokens_details.cached_tokens === "number" ? usage.input_tokens_details.cached_tokens : 0;
990
- return {
1148
+ const outputDetails = isRecord2(usage.output_tokens_details) ? usage.output_tokens_details : undefined;
1149
+ const inputDetails = isRecord2(usage.input_tokens_details) ? usage.input_tokens_details : undefined;
1150
+ const costDetails = isRecord2(usage.cost_details) ? usage.cost_details : undefined;
1151
+ const normalized = {
991
1152
  cacheReadInputTokens: cached,
1153
+ cacheWriteInputTokens: inputDetails && typeof inputDetails.cache_write_tokens === "number" ? inputDetails.cache_write_tokens : undefined,
1154
+ costCredits: typeof usage.cost === "number" ? usage.cost : undefined,
992
1155
  inputTokens: Math.max(0, input - cached),
993
- outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0
1156
+ outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
1157
+ reasoningTokens: outputDetails && typeof outputDetails.reasoning_tokens === "number" ? outputDetails.reasoning_tokens : undefined,
1158
+ upstreamInferenceCostCredits: costDetails && typeof costDetails.upstream_inference_cost === "number" ? costDetails.upstream_inference_cost : undefined
994
1159
  };
1160
+ if (isRecord2(usage.server_tool_use)) {
1161
+ normalized.serverToolUse = Object.fromEntries(Object.entries(usage.server_tool_use).filter((entry) => typeof entry[1] === "number"));
1162
+ }
1163
+ return normalized;
1164
+ };
1165
+ var extractResponseMetadata = (response) => {
1166
+ const providerMetadata = isRecord2(response.openrouter_metadata) ? response.openrouter_metadata : undefined;
1167
+ const generationId = typeof response.id === "string" ? response.id : undefined;
1168
+ const model = typeof response.model === "string" ? response.model : undefined;
1169
+ const provider = typeof response.provider === "string" ? response.provider : undefined;
1170
+ const serviceTier = typeof response.service_tier === "string" ? response.service_tier : undefined;
1171
+ if (!providerMetadata && !generationId && !model && !provider && !serviceTier)
1172
+ return;
1173
+ return { generationId, model, provider, providerMetadata, serviceTier };
995
1174
  };
996
1175
  var extractMimeFormat = (mimeType) => {
997
1176
  if (typeof mimeType !== "string") {
@@ -1048,6 +1227,7 @@ var processFunctionCallArgumentsDone = function* (parsed, pendingCalls) {
1048
1227
  id: callId || pending?.callId || itemId,
1049
1228
  input: parseToolInput2(args),
1050
1229
  name,
1230
+ providerData: pending?.providerData ? { ...pending.providerData, arguments: args } : undefined,
1051
1231
  type: "tool_use"
1052
1232
  };
1053
1233
  };
@@ -1066,9 +1246,21 @@ var processOutputItemAdded = (parsed, pendingCalls) => {
1066
1246
  pendingCalls.set(itemId, {
1067
1247
  arguments: "",
1068
1248
  callId,
1069
- name
1249
+ name,
1250
+ providerData: { ...item }
1070
1251
  });
1071
1252
  };
1253
+ var processOutputItemDone = function* (parsed) {
1254
+ if (!isRecord2(parsed.item) || typeof parsed.item.type !== "string")
1255
+ return;
1256
+ if (!parsed.item.type.startsWith("openrouter:"))
1257
+ return;
1258
+ yield {
1259
+ data: { ...parsed.item },
1260
+ provider: "openrouter",
1261
+ type: "provider_event"
1262
+ };
1263
+ };
1072
1264
  var isCompletedImageGeneration = (item) => item.type === "image_generation_call" && item.status === "completed" && typeof item.result === "string" && item.result !== "";
1073
1265
  var buildImageChunk = (item) => ({
1074
1266
  data: typeof item.result === "string" ? item.result : "",
@@ -1084,6 +1276,30 @@ var extractImageFromOutput = function* (output) {
1084
1276
  yield buildImageChunk(item);
1085
1277
  }
1086
1278
  };
1279
+ var extractCitationsFromOutput = function* (output) {
1280
+ for (const item of output) {
1281
+ if (!isRecordArray2(item.content))
1282
+ continue;
1283
+ for (const content of item.content) {
1284
+ if (!Array.isArray(content.annotations))
1285
+ continue;
1286
+ for (const annotation of content.annotations) {
1287
+ if (!isRecord2(annotation) || annotation.type !== "url_citation")
1288
+ continue;
1289
+ if (typeof annotation.url !== "string")
1290
+ continue;
1291
+ yield {
1292
+ content: typeof annotation.content === "string" ? annotation.content : undefined,
1293
+ endIndex: typeof annotation.end_index === "number" ? annotation.end_index : undefined,
1294
+ startIndex: typeof annotation.start_index === "number" ? annotation.start_index : undefined,
1295
+ title: typeof annotation.title === "string" ? annotation.title : undefined,
1296
+ type: "citation",
1297
+ url: annotation.url
1298
+ };
1299
+ }
1300
+ }
1301
+ }
1302
+ };
1087
1303
  var processCompleted = function* (parsed) {
1088
1304
  if (!isRecord2(parsed.response)) {
1089
1305
  yield { type: "done", usage: undefined };
@@ -1091,12 +1307,26 @@ var processCompleted = function* (parsed) {
1091
1307
  }
1092
1308
  const { response } = parsed;
1093
1309
  const usage = extractUsage2(response);
1310
+ const metadata = extractResponseMetadata(response);
1094
1311
  if (isRecordArray2(response.output)) {
1312
+ yield* extractCitationsFromOutput(response.output);
1095
1313
  yield* extractImageFromOutput(response.output);
1096
1314
  }
1097
- yield { type: "done", usage };
1315
+ yield { metadata, type: "done", usage };
1316
+ };
1317
+ var responseFailure = (eventType, parsed, providerName) => {
1318
+ const response = isRecord2(parsed.response) ? parsed.response : parsed;
1319
+ const error = isRecord2(response.error) ? response.error : undefined;
1320
+ const type = typeof response.error_type === "string" ? response.error_type : error && typeof error.code === "string" ? error.code : eventType;
1321
+ return new ProviderError({
1322
+ message: error && typeof error.message === "string" ? error.message : `OpenRouter Responses API: ${eventType}`,
1323
+ metadata: response,
1324
+ provider: providerName,
1325
+ retryable: type === "rate_limit_exceeded" || type === "provider_overloaded" || type === "provider_unavailable" || type === "server",
1326
+ type
1327
+ });
1098
1328
  };
1099
- var processSSEEvent = function* (eventType, parsed, pendingCalls) {
1329
+ var processSSEEvent = function* (eventType, parsed, pendingCalls, providerName) {
1100
1330
  switch (eventType) {
1101
1331
  case "response.reasoning_summary_text.delta": {
1102
1332
  const delta = typeof parsed.delta === "string" ? parsed.delta : "";
@@ -1117,6 +1347,9 @@ var processSSEEvent = function* (eventType, parsed, pendingCalls) {
1117
1347
  case "response.output_item.added":
1118
1348
  processOutputItemAdded(parsed, pendingCalls);
1119
1349
  break;
1350
+ case "response.output_item.done":
1351
+ yield* processOutputItemDone(parsed);
1352
+ break;
1120
1353
  case "response.function_call_arguments.delta":
1121
1354
  processFunctionCallArgumentsDelta(parsed, pendingCalls);
1122
1355
  break;
@@ -1127,11 +1360,10 @@ var processSSEEvent = function* (eventType, parsed, pendingCalls) {
1127
1360
  yield* processCompleted(parsed);
1128
1361
  break;
1129
1362
  case "response.failed":
1130
- case "response.incomplete": {
1131
- const respObj = isRecord2(parsed.response) ? parsed.response : parsed;
1132
- const errMsg = isRecord2(respObj.error) && typeof respObj.error.message === "string" ? respObj.error.message : `OpenAI Responses API: ${eventType}`;
1133
- throw new Error(errMsg);
1134
- }
1363
+ case "response.incomplete":
1364
+ case "response.error":
1365
+ case "error":
1366
+ throw responseFailure(eventType, parsed, providerName);
1135
1367
  }
1136
1368
  };
1137
1369
  var flushSSEBuffer = function* (state) {
@@ -1140,7 +1372,7 @@ var flushSSEBuffer = function* (state) {
1140
1372
  }
1141
1373
  const parsed = parseJSON(state.buffer);
1142
1374
  if (parsed) {
1143
- yield* processSSEEvent(state.currentEvent, parsed, state.pendingCalls);
1375
+ yield* processSSEEvent(state.currentEvent, parsed, state.pendingCalls, state.providerName);
1144
1376
  }
1145
1377
  state.currentEvent = "";
1146
1378
  state.buffer = "";
@@ -1178,41 +1410,47 @@ var drainReader2 = async function* (reader, decoder, state, signal) {
1178
1410
  yield* processSSELines2([textBuffer, ""], state);
1179
1411
  }
1180
1412
  };
1181
- var parseSSEStream2 = async function* (body, signal) {
1413
+ var parseSSEStream2 = async function* (body, providerName, signal) {
1182
1414
  const reader = body.getReader();
1183
1415
  const decoder = new TextDecoder;
1184
1416
  const state = {
1185
1417
  buffer: "",
1186
1418
  currentEvent: "",
1187
1419
  pendingCalls: new Map,
1188
- usage: undefined
1420
+ usage: undefined,
1421
+ providerName
1189
1422
  };
1190
1423
  try {
1191
1424
  yield* drainReader2(reader, decoder, state, signal);
1425
+ yield* flushSSEBuffer(state);
1192
1426
  } finally {
1193
1427
  reader.releaseLock();
1194
1428
  }
1195
1429
  };
1196
- var fetchResponsesStream = async function* (baseUrl, apiKey, body, signal) {
1430
+ var fetchResponsesStream = async function* (baseUrl, apiKey, body, fetchImpl, headers, providerName, signal) {
1197
1431
  const target = `${baseUrl}/v1/responses`;
1198
- const response = await fetch(target, {
1432
+ const requestHeaders = new Headers(headers);
1433
+ requestHeaders.set("Authorization", `Bearer ${apiKey}`);
1434
+ requestHeaders.set("Content-Type", "application/json");
1435
+ const response = await fetchImpl(target, {
1199
1436
  ...h2IfHttps2(target),
1200
1437
  body: JSON.stringify(body),
1201
- headers: {
1202
- Authorization: `Bearer ${apiKey}`,
1203
- "Content-Type": "application/json"
1204
- },
1438
+ headers: requestHeaders,
1205
1439
  method: "POST",
1206
1440
  signal
1207
1441
  });
1208
1442
  if (!response.ok) {
1209
1443
  const errorText = await response.text();
1210
- throw new Error(`OpenAI Responses API error ${response.status}: ${errorText}`);
1444
+ throw ProviderError.fromResponse(providerName, response.status, errorText);
1211
1445
  }
1212
1446
  if (!response.body) {
1213
- throw new Error("OpenAI Responses API returned no response body");
1447
+ throw new ProviderError({
1448
+ message: `${providerName} Responses API returned no response body`,
1449
+ provider: providerName,
1450
+ retryable: true
1451
+ });
1214
1452
  }
1215
- yield* parseSSEStream2(response.body, signal);
1453
+ yield* parseSSEStream2(response.body, providerName, signal);
1216
1454
  };
1217
1455
  var resolveImageModels = (imageModels) => {
1218
1456
  if (!imageModels) {
@@ -1224,15 +1462,28 @@ var resolveImageModels = (imageModels) => {
1224
1462
  return new Set(imageModels);
1225
1463
  };
1226
1464
  var openaiResponses = (config2) => {
1465
+ if (!config2.apiKey && !config2.tokenSource)
1466
+ throw new Error("openaiResponses() requires either apiKey or tokenSource");
1227
1467
  const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL2;
1468
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
1228
1469
  const imageModels = resolveImageModels(config2.imageModels);
1470
+ const providerName = config2.providerName ?? "openai-responses";
1471
+ const resolveKey = async () => config2.tokenSource ? await Promise.resolve(config2.tokenSource()) : config2.apiKey;
1472
+ const resolveHeaders = async (params) => typeof config2.headers === "function" ? await config2.headers(params) : config2.headers ?? {};
1229
1473
  return instrumentAIProvider({
1230
1474
  stream: (params) => {
1231
1475
  const isImageModel = imageModels.has(params.model);
1232
- const body = buildRequestBody2(params, isImageModel);
1233
- return fetchResponsesStream(baseUrl, config2.apiKey, body, params.signal);
1476
+ const builtBody = buildRequestBody2(params, isImageModel, config2.modelForCapabilities?.(params.model) ?? params.model);
1477
+ const body = config2.transformRequestBody ? config2.transformRequestBody(builtBody, params) : builtBody;
1478
+ return async function* () {
1479
+ const [apiKey, headers] = await Promise.all([
1480
+ resolveKey(),
1481
+ resolveHeaders(params)
1482
+ ]);
1483
+ yield* fetchResponsesStream(baseUrl, apiKey, body, fetchImpl, headers, providerName, params.signal);
1484
+ }();
1234
1485
  }
1235
- }, "openai-responses");
1486
+ }, providerName);
1236
1487
  };
1237
1488
 
1238
1489
  // src/ai/providers/gemini.ts
@@ -1251,14 +1502,43 @@ var mapContentBlock = (block) => {
1251
1502
  case "text":
1252
1503
  return { text: block.content };
1253
1504
  case "image":
1254
- return {
1505
+ return block.source.type === "url" ? {
1506
+ fileData: {
1507
+ fileUri: block.source.url,
1508
+ mimeType: block.source.media_type
1509
+ }
1510
+ } : {
1255
1511
  inlineData: {
1256
1512
  data: block.source.data,
1257
1513
  mimeType: block.source.media_type
1258
1514
  }
1259
1515
  };
1260
1516
  case "document":
1517
+ return block.source.type === "url" ? {
1518
+ fileData: {
1519
+ fileUri: block.source.url,
1520
+ mimeType: block.source.media_type
1521
+ }
1522
+ } : {
1523
+ inlineData: {
1524
+ data: block.source.data,
1525
+ mimeType: block.source.media_type
1526
+ }
1527
+ };
1528
+ case "audio":
1261
1529
  return {
1530
+ inlineData: {
1531
+ data: block.source.data,
1532
+ mimeType: `audio/${block.source.format}`
1533
+ }
1534
+ };
1535
+ case "video":
1536
+ return block.source.type === "url" ? {
1537
+ fileData: {
1538
+ fileUri: block.source.url,
1539
+ mimeType: block.source.media_type
1540
+ }
1541
+ } : {
1262
1542
  inlineData: {
1263
1543
  data: block.source.data,
1264
1544
  mimeType: block.source.media_type
@@ -1564,6 +1844,8 @@ var mapContentBlock2 = (block) => {
1564
1844
  };
1565
1845
  }
1566
1846
  if (block.type === "tool_use") {
1847
+ if (block.providerData)
1848
+ return { ...block.providerData };
1567
1849
  return {
1568
1850
  id: block.id,
1569
1851
  input: block.input,
@@ -1571,6 +1853,12 @@ var mapContentBlock2 = (block) => {
1571
1853
  type: "tool_use"
1572
1854
  };
1573
1855
  }
1856
+ if (block.type === "audio" || block.type === "video") {
1857
+ throw new Error(`Anthropic does not support ${block.type} content blocks`);
1858
+ }
1859
+ if (block.type === "provider_data") {
1860
+ return { ...block.data };
1861
+ }
1574
1862
  return { text: block.content, type: "text" };
1575
1863
  };
1576
1864
  var mapMessage = (msg) => ({
@@ -1744,11 +2032,15 @@ var handleContentBlockStart = (parsed, state) => {
1744
2032
  state.currentToolName = getString(block, "name");
1745
2033
  state.toolInputJson = "";
1746
2034
  state.isThinkingBlock = false;
2035
+ state.currentProviderBlock = undefined;
1747
2036
  } else if (block && block.type === "thinking") {
1748
2037
  state.isThinkingBlock = true;
1749
2038
  state.thinkingSignature = "";
2039
+ state.currentProviderBlock = undefined;
1750
2040
  } else {
1751
2041
  state.isThinkingBlock = false;
2042
+ state.currentProviderBlock = block && block.type !== "text" ? { ...block } : undefined;
2043
+ state.providerBlockInputJson = "";
1752
2044
  }
1753
2045
  };
1754
2046
  var handleContentBlockDelta = (parsed, state) => {
@@ -1769,7 +2061,11 @@ var handleContentBlockDelta = (parsed, state) => {
1769
2061
  };
1770
2062
  }
1771
2063
  if (delta.type === "input_json_delta") {
1772
- state.toolInputJson += getString(delta, "partial_json");
2064
+ if (state.currentProviderBlock) {
2065
+ state.providerBlockInputJson += getString(delta, "partial_json");
2066
+ } else {
2067
+ state.toolInputJson += getString(delta, "partial_json");
2068
+ }
1773
2069
  }
1774
2070
  if (delta.type === "signature_delta") {
1775
2071
  state.thinkingSignature += getString(delta, "signature");
@@ -1787,6 +2083,19 @@ var handleContentBlockStop = (state) => {
1787
2083
  type: "thinking"
1788
2084
  };
1789
2085
  }
2086
+ if (state.currentProviderBlock) {
2087
+ const data = { ...state.currentProviderBlock };
2088
+ if (state.providerBlockInputJson) {
2089
+ data.input = tryParseJson(state.providerBlockInputJson) ?? state.providerBlockInputJson;
2090
+ }
2091
+ state.currentProviderBlock = undefined;
2092
+ state.providerBlockInputJson = "";
2093
+ return {
2094
+ data,
2095
+ provider: state.providerName,
2096
+ type: "provider_event"
2097
+ };
2098
+ }
1790
2099
  if (!state.currentToolId) {
1791
2100
  return;
1792
2101
  }
@@ -1806,11 +2115,39 @@ var extractUsage4 = (usageRecord, existingUsage) => {
1806
2115
  if (!usageRecord) {
1807
2116
  return existingUsage;
1808
2117
  }
1809
- return {
2118
+ const normalized = {
1810
2119
  cacheReadInputTokens: getNumber(usageRecord, "cache_read_input_tokens") || existingUsage?.cacheReadInputTokens || 0,
1811
2120
  cacheWriteInputTokens: getNumber(usageRecord, "cache_creation_input_tokens") || existingUsage?.cacheWriteInputTokens || 0,
1812
2121
  inputTokens: getNumber(usageRecord, "input_tokens") || existingUsage?.inputTokens || 0,
1813
- outputTokens: getNumber(usageRecord, "output_tokens") || existingUsage?.outputTokens || 0
2122
+ outputTokens: getNumber(usageRecord, "output_tokens") || existingUsage?.outputTokens || 0,
2123
+ costCredits: getNumber(usageRecord, "cost") || existingUsage?.costCredits,
2124
+ reasoningTokens: getNumber(usageRecord, "reasoning_tokens") || existingUsage?.reasoningTokens,
2125
+ upstreamInferenceCostCredits: getNumber(getRecord(usageRecord, "cost_details") ?? {}, "upstream_inference_cost") || existingUsage?.upstreamInferenceCostCredits
2126
+ };
2127
+ const serverToolUse = getRecord(usageRecord, "server_tool_use");
2128
+ if (serverToolUse) {
2129
+ normalized.serverToolUse = Object.fromEntries(Object.entries(serverToolUse).filter((entry) => typeof entry[1] === "number"));
2130
+ }
2131
+ return normalized;
2132
+ };
2133
+ var mergeMetadata = (source, state) => {
2134
+ const providerMetadata = getRecord(source, "openrouter_metadata");
2135
+ const generationId = getString(source, "id") || undefined;
2136
+ const model = getString(source, "model") || undefined;
2137
+ const provider = getString(source, "provider") || undefined;
2138
+ const serviceTier = getString(source, "service_tier") || undefined;
2139
+ if (!providerMetadata && !generationId && !model && !provider && !serviceTier)
2140
+ return;
2141
+ state.metadata = {
2142
+ ...state.metadata,
2143
+ generationId: generationId ?? state.metadata?.generationId,
2144
+ model: model ?? state.metadata?.model,
2145
+ provider: provider ?? state.metadata?.provider,
2146
+ providerMetadata: {
2147
+ ...state.metadata?.providerMetadata,
2148
+ ...providerMetadata
2149
+ },
2150
+ serviceTier: serviceTier ?? state.metadata?.serviceTier
1814
2151
  };
1815
2152
  };
1816
2153
  var handleMessageDelta = (parsed, state) => {
@@ -1828,15 +2165,18 @@ var handleMessageStart = (parsed, state) => {
1828
2165
  }
1829
2166
  const startUsage = getRecord(message, "usage");
1830
2167
  state.usage = extractUsage4(startUsage, state.usage);
2168
+ mergeMetadata(message, state);
1831
2169
  };
1832
- var handleError = (parsed) => {
2170
+ var handleError = (parsed, state) => {
1833
2171
  const error = getRecord(parsed, "error");
1834
2172
  const errorMessage = error ? getString(error, "message") : "";
1835
- const errorType = error ? getString(error, "type") : "";
1836
- const retryable = errorType === "overloaded_error" || errorType === "rate_limit_error" || errorType === "api_error";
2173
+ const nativeErrorType = error ? getString(error, "type") : "";
2174
+ const errorType = error ? getString(error, "error_type") || nativeErrorType : "";
2175
+ const retryable = errorType === "provider_overloaded" || errorType === "rate_limit_exceeded" || errorType === "provider_unavailable" || errorType === "server" || nativeErrorType === "overloaded_error" || nativeErrorType === "rate_limit_error" || nativeErrorType === "api_error";
1837
2176
  throw new ProviderError({
1838
2177
  message: errorMessage || "Anthropic API error",
1839
- provider: "anthropic",
2178
+ metadata: error,
2179
+ provider: state.providerName,
1840
2180
  retryable,
1841
2181
  type: errorType || null
1842
2182
  });
@@ -1855,6 +2195,7 @@ var processEvent = (eventType, parsed, state) => {
1855
2195
  }
1856
2196
  case "message_delta": {
1857
2197
  handleMessageDelta(parsed, state);
2198
+ mergeMetadata(parsed, state);
1858
2199
  return;
1859
2200
  }
1860
2201
  case "message_start": {
@@ -1862,14 +2203,16 @@ var processEvent = (eventType, parsed, state) => {
1862
2203
  return;
1863
2204
  }
1864
2205
  case "message_stop": {
2206
+ mergeMetadata(parsed, state);
1865
2207
  return {
1866
2208
  stopReason: state.stopReason,
2209
+ metadata: state.metadata,
1867
2210
  type: "done",
1868
2211
  usage: state.usage
1869
2212
  };
1870
2213
  }
1871
2214
  case "error": {
1872
- handleError(parsed);
2215
+ handleError(parsed, state);
1873
2216
  return;
1874
2217
  }
1875
2218
  default: {
@@ -1934,7 +2277,7 @@ async function* streamChunks(reader, decoder, state, signal) {
1934
2277
  yield* result.chunks;
1935
2278
  }
1936
2279
  }
1937
- async function* parseSSEStream4(body, signal) {
2280
+ async function* parseSSEStream4(body, providerName, signal) {
1938
2281
  const reader = body.getReader();
1939
2282
  const decoder = new TextDecoder;
1940
2283
  const state = {
@@ -1945,7 +2288,9 @@ async function* parseSSEStream4(body, signal) {
1945
2288
  stopReason: "",
1946
2289
  thinkingSignature: "",
1947
2290
  toolInputJson: "",
1948
- usage: undefined
2291
+ usage: undefined,
2292
+ providerName,
2293
+ providerBlockInputJson: ""
1949
2294
  };
1950
2295
  try {
1951
2296
  yield* streamChunks(reader, decoder, state, signal);
@@ -1953,41 +2298,51 @@ async function* parseSSEStream4(body, signal) {
1953
2298
  reader.releaseLock();
1954
2299
  }
1955
2300
  }
1956
- var fetchAndStream = async function* (baseUrl, config2, params, configuredMax, promptCaching) {
1957
- const body = buildRequestBody4(params, configuredMax, promptCaching);
2301
+ var fetchAndStream = async function* (baseUrl, config2, params, configuredMax, promptCaching, providerName) {
2302
+ const builtBody = buildRequestBody4(params, configuredMax, promptCaching);
2303
+ const body = config2.transformRequestBody ? config2.transformRequestBody(builtBody, params) : builtBody;
1958
2304
  const target = `${baseUrl}/v1/messages`;
1959
2305
  const fetchImpl = config2.fetch ?? fetch;
2306
+ const token = config2.tokenSource ? await Promise.resolve(config2.tokenSource()) : config2.apiKey;
2307
+ const suppliedHeaders = typeof config2.headers === "function" ? await config2.headers(params) : config2.headers ?? {};
2308
+ const requestHeaders = new Headers(suppliedHeaders);
2309
+ requestHeaders.set("Content-Type", "application/json");
2310
+ if (config2.authStyle === "bearer") {
2311
+ requestHeaders.set("Authorization", `Bearer ${token}`);
2312
+ } else {
2313
+ requestHeaders.set("anthropic-version", API_VERSION);
2314
+ requestHeaders.set("x-api-key", token);
2315
+ }
1960
2316
  const response = await fetchImpl(target, {
1961
2317
  ...h2IfHttps4(target),
1962
2318
  body: JSON.stringify(body),
1963
- headers: {
1964
- "anthropic-version": API_VERSION,
1965
- "Content-Type": "application/json",
1966
- "x-api-key": config2.apiKey
1967
- },
2319
+ headers: requestHeaders,
1968
2320
  method: "POST",
1969
2321
  signal: params.signal
1970
2322
  });
1971
2323
  if (!response.ok) {
1972
2324
  const errorText = await response.text();
1973
- throw ProviderError.fromResponse("anthropic", response.status, errorText);
2325
+ throw ProviderError.fromResponse(providerName, response.status, errorText);
1974
2326
  }
1975
2327
  if (!response.body) {
1976
2328
  throw new ProviderError({
1977
- message: "Anthropic API returned no response body",
1978
- provider: "anthropic",
2329
+ message: `${providerName} Messages API returned no response body`,
2330
+ provider: providerName,
1979
2331
  retryable: true
1980
2332
  });
1981
2333
  }
1982
- yield* parseSSEStream4(response.body, params.signal);
2334
+ yield* parseSSEStream4(response.body, providerName, params.signal);
1983
2335
  };
1984
2336
  var anthropic = (config2) => {
2337
+ if (!config2.apiKey && !config2.tokenSource)
2338
+ throw new Error("anthropic() requires either apiKey or tokenSource");
1985
2339
  const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL4;
1986
2340
  const configuredMax = config2.maxTokens ?? DEFAULT_MAX_TOKENS;
1987
2341
  const promptCaching = config2.promptCaching ?? true;
2342
+ const providerName = config2.providerName ?? "anthropic";
1988
2343
  return instrumentAIProvider({
1989
- stream: (params) => fetchAndStream(baseUrl, config2, params, configuredMax, promptCaching)
1990
- }, "anthropic");
2344
+ stream: (params) => fetchAndStream(baseUrl, config2, params, configuredMax, promptCaching, providerName)
2345
+ }, providerName);
1991
2346
  };
1992
2347
 
1993
2348
  // src/ai/providers/ollama.ts
@@ -2228,6 +2583,612 @@ var ollama = (config2 = {}) => {
2228
2583
  }, "ollama");
2229
2584
  };
2230
2585
 
2586
+ // src/ai/providers/openrouterClient.ts
2587
+ var DEFAULT_BASE_URL6 = "https://openrouter.ai/api/v1";
2588
+ var withoutLatestPrefix = (model) => model.startsWith("~") ? model.slice(1) : model;
2589
+ var openRouterModelMatchesRule = (model, rule) => {
2590
+ const normalizedModel = withoutLatestPrefix(model);
2591
+ const normalizedRule = withoutLatestPrefix(rule);
2592
+ return normalizedRule.endsWith("/*") ? normalizedModel.startsWith(normalizedRule.slice(0, -1)) : normalizedModel === normalizedRule;
2593
+ };
2594
+ var assertAllowedModel = (model, allowedModels) => {
2595
+ if (!allowedModels)
2596
+ return;
2597
+ if (allowedModels.some((rule) => openRouterModelMatchesRule(model, rule)))
2598
+ return;
2599
+ throw new Error(`OpenRouter model "${model}" is not allowed`);
2600
+ };
2601
+ var normalizePath = (path) => path.startsWith("/") ? path : `/${path}`;
2602
+ var encodeModelPath = (model) => model.split("/").map(encodeURIComponent).join("/");
2603
+ var withQuery = (url, query) => {
2604
+ if (!query)
2605
+ return url;
2606
+ const result = new URL(url);
2607
+ for (const [key, value] of Object.entries(query)) {
2608
+ if (value !== undefined)
2609
+ result.searchParams.set(key, String(value));
2610
+ }
2611
+ return result.toString();
2612
+ };
2613
+ var parseImageSSE = async function* (response) {
2614
+ if (!response.body)
2615
+ throw new Error("OpenRouter image stream has no body");
2616
+ const reader = response.body.getReader();
2617
+ const decoder = new TextDecoder;
2618
+ let buffer = "";
2619
+ try {
2620
+ for (;; ) {
2621
+ const result = await reader.read();
2622
+ buffer += decoder.decode(result.value, { stream: !result.done });
2623
+ const lines = buffer.split(`
2624
+ `);
2625
+ buffer = lines.pop() ?? "";
2626
+ for (const line of lines) {
2627
+ if (!line.startsWith("data: "))
2628
+ continue;
2629
+ const data = line.slice(6);
2630
+ if (data === "[DONE]")
2631
+ return;
2632
+ try {
2633
+ const parsed = JSON.parse(data);
2634
+ if (parsed && typeof parsed === "object" && "type" in parsed)
2635
+ yield parsed;
2636
+ } catch {}
2637
+ }
2638
+ if (result.done)
2639
+ break;
2640
+ }
2641
+ if (buffer.startsWith("data: ")) {
2642
+ const parsed = JSON.parse(buffer.slice(6));
2643
+ if (parsed && typeof parsed === "object" && "type" in parsed)
2644
+ yield parsed;
2645
+ }
2646
+ } finally {
2647
+ reader.releaseLock();
2648
+ }
2649
+ };
2650
+ var toBytes = (value) => typeof value === "string" ? new TextEncoder().encode(value) : value;
2651
+ var hexToBytes = (hex) => {
2652
+ if (!/^[0-9a-f]+$/iu.test(hex) || hex.length % 2 !== 0)
2653
+ return;
2654
+ const bytes = new Uint8Array(hex.length / 2);
2655
+ for (let index = 0;index < bytes.length; index += 1) {
2656
+ bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
2657
+ }
2658
+ return bytes;
2659
+ };
2660
+ var constantTimeEqual = (left, right) => {
2661
+ if (left.length !== right.length)
2662
+ return false;
2663
+ let mismatch = 0;
2664
+ for (let index = 0;index < left.length; index += 1)
2665
+ mismatch |= (left[index] ?? 0) ^ (right[index] ?? 0);
2666
+ return mismatch === 0;
2667
+ };
2668
+ var verifyOpenRouterWebhookSignature = async (options) => {
2669
+ const fields = new Map(options.header.split(",").map((part) => {
2670
+ const [key2, ...rest] = part.trim().split("=");
2671
+ return [key2, rest.join("=")];
2672
+ }));
2673
+ const timestamp = fields.get("t");
2674
+ const supplied = fields.get("v1");
2675
+ if (!timestamp || !supplied)
2676
+ return false;
2677
+ const timestampNumber = Number(timestamp);
2678
+ const now = options.nowSeconds ?? Math.floor(Date.now() / 1000);
2679
+ const tolerance = options.toleranceSeconds ?? 300;
2680
+ if (!Number.isFinite(timestampNumber) || Math.abs(now - timestampNumber) > tolerance)
2681
+ return false;
2682
+ const key = await crypto.subtle.importKey("raw", Uint8Array.from(toBytes(options.secret)).buffer, { hash: "SHA-256", name: "HMAC" }, false, ["sign"]);
2683
+ const prefix = new TextEncoder().encode(`${timestamp},`);
2684
+ const body = toBytes(options.body);
2685
+ const payload = new Uint8Array(prefix.length + body.length);
2686
+ payload.set(prefix);
2687
+ payload.set(body, prefix.length);
2688
+ const expected = new Uint8Array(await crypto.subtle.sign("HMAC", key, payload.buffer));
2689
+ const suppliedBytes = hexToBytes(supplied);
2690
+ return suppliedBytes ? constantTimeEqual(expected, suppliedBytes) : false;
2691
+ };
2692
+ var createOpenRouterClient = (config2) => {
2693
+ if (!config2.apiKey && !config2.tokenSource)
2694
+ throw new Error("createOpenRouterClient() requires either apiKey or tokenSource");
2695
+ const baseUrl = (config2.baseUrl ?? DEFAULT_BASE_URL6).replace(/\/$/, "");
2696
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
2697
+ const allowedModels = config2.allowedModels ? [...config2.allowedModels] : undefined;
2698
+ const requestRaw = async (path, options = {}) => {
2699
+ const token = config2.tokenSource ? await Promise.resolve(config2.tokenSource()) : config2.apiKey;
2700
+ const suppliedHeaders = typeof config2.headers === "function" ? await config2.headers() : config2.headers ?? {};
2701
+ const headers = new Headers(suppliedHeaders);
2702
+ new Headers(options.headers).forEach((value, key) => headers.set(key, value));
2703
+ headers.set("Authorization", `Bearer ${token}`);
2704
+ let body;
2705
+ if (options.body instanceof FormData || options.body instanceof Blob) {
2706
+ body = options.body;
2707
+ } else if (options.body !== undefined) {
2708
+ headers.set("Content-Type", "application/json");
2709
+ body = JSON.stringify(options.body);
2710
+ }
2711
+ const { query, ...requestInit } = options;
2712
+ const response = await fetchImpl(withQuery(`${baseUrl}${normalizePath(path)}`, options.query), { ...requestInit, body, headers });
2713
+ if (!response.ok) {
2714
+ throw ProviderError.fromResponse("openrouter", response.status, await response.text());
2715
+ }
2716
+ return response;
2717
+ };
2718
+ const request = async (path, options = {}) => (await requestRaw(path, options)).json();
2719
+ const listModels = async (query) => {
2720
+ const result = await request("/models", { query });
2721
+ if (!allowedModels)
2722
+ return result;
2723
+ return {
2724
+ ...result,
2725
+ data: result.data.filter((model) => allowedModels.some((rule) => openRouterModelMatchesRule(model.id, rule)))
2726
+ };
2727
+ };
2728
+ const filterModelList = (result) => {
2729
+ if (!allowedModels)
2730
+ return result;
2731
+ return {
2732
+ ...result,
2733
+ data: result.data.filter((model) => allowedModels.some((rule) => openRouterModelMatchesRule(model.id, rule)))
2734
+ };
2735
+ };
2736
+ return {
2737
+ cancelBatch: (id) => request(`/batches/${encodeURIComponent(id)}/cancel`, {
2738
+ method: "POST"
2739
+ }),
2740
+ createBatch: (body) => request("/batches", { body, method: "POST" }),
2741
+ createEmbedding: (body) => {
2742
+ assertAllowedModel(body.model, allowedModels);
2743
+ return request("/embeddings", {
2744
+ body,
2745
+ method: "POST"
2746
+ });
2747
+ },
2748
+ generateImage: (body) => {
2749
+ assertAllowedModel(body.model, allowedModels);
2750
+ return request("/images", {
2751
+ body,
2752
+ method: "POST"
2753
+ });
2754
+ },
2755
+ deleteFile: (id, workspaceId) => request(`/files/${encodeURIComponent(id)}`, { method: "DELETE", query: { workspace_id: workspaceId } }),
2756
+ downloadFile: (id, workspaceId) => requestRaw(`/files/${encodeURIComponent(id)}/content`, {
2757
+ query: { workspace_id: workspaceId }
2758
+ }),
2759
+ downloadVideo: (id, index = 0) => requestRaw(`/videos/${encodeURIComponent(id)}/content`, {
2760
+ query: { index }
2761
+ }),
2762
+ generateVideo: (body) => {
2763
+ assertAllowedModel(body.model, allowedModels);
2764
+ return request("/videos", {
2765
+ body,
2766
+ method: "POST"
2767
+ });
2768
+ },
2769
+ getBatch: (id) => request(`/batches/${encodeURIComponent(id)}`),
2770
+ getCredits: () => request("/credits"),
2771
+ getCurrentKey: () => request("/key"),
2772
+ getFile: (id, workspaceId) => request(`/files/${encodeURIComponent(id)}`, {
2773
+ query: { workspace_id: workspaceId }
2774
+ }),
2775
+ getGeneration: (id) => request("/generation", {
2776
+ query: { id }
2777
+ }),
2778
+ getModelEndpoints: (model) => {
2779
+ assertAllowedModel(model, allowedModels);
2780
+ return request(`/models/${encodeModelPath(model)}/endpoints`);
2781
+ },
2782
+ getModel: (model) => {
2783
+ assertAllowedModel(model, allowedModels);
2784
+ return request(`/model/${encodeModelPath(model)}`);
2785
+ },
2786
+ getImageModelEndpoints: (model) => {
2787
+ assertAllowedModel(model, allowedModels);
2788
+ return request(`/images/models/${encodeModelPath(model)}/endpoints`);
2789
+ },
2790
+ getVideo: (id) => request(`/videos/${encodeURIComponent(id)}`),
2791
+ listImageModels: async () => filterModelList(await request("/images/models")),
2792
+ listFiles: (query) => request("/files", { query }),
2793
+ listModels,
2794
+ listUserModels: async () => filterModelList(await request("/models/user")),
2795
+ listZdrEndpoints: async () => {
2796
+ const result = await request("/endpoints/zdr");
2797
+ if (!allowedModels)
2798
+ return result;
2799
+ return {
2800
+ ...result,
2801
+ data: result.data.filter((endpoint) => allowedModels.some((rule) => openRouterModelMatchesRule(endpoint.model_id, rule)))
2802
+ };
2803
+ },
2804
+ countModels: (outputModalities) => request("/models/count", {
2805
+ query: { output_modalities: outputModalities }
2806
+ }),
2807
+ listPresets: (offset = 0, limit = 100) => request("/presets", { query: { limit, offset } }),
2808
+ listPresetVersions: (slug, offset = 0, limit = 100) => request(`/presets/${encodeURIComponent(slug)}/versions`, { query: { limit, offset } }),
2809
+ listProviders: () => request("/providers"),
2810
+ listRerankModels: async () => filterModelList(await request("/rerank/models")),
2811
+ listVideoModels: async () => filterModelList(await request("/videos/models")),
2812
+ request,
2813
+ requestRaw,
2814
+ streamImage: async function* (body, options = {}) {
2815
+ assertAllowedModel(body.model, allowedModels);
2816
+ const response = await requestRaw("/images", {
2817
+ ...options,
2818
+ body: { ...body, stream: true },
2819
+ method: "POST"
2820
+ });
2821
+ yield* parseImageSSE(response);
2822
+ },
2823
+ respond: (body) => {
2824
+ assertAllowedModel(body.model, allowedModels);
2825
+ return body.stream ? requestRaw("/responses", { body, method: "POST" }) : request("/responses", {
2826
+ body,
2827
+ method: "POST"
2828
+ });
2829
+ },
2830
+ rerank: (body) => {
2831
+ assertAllowedModel(body.model, allowedModels);
2832
+ return request("/rerank", {
2833
+ body,
2834
+ method: "POST"
2835
+ });
2836
+ },
2837
+ speak: (body) => {
2838
+ assertAllowedModel(body.model, allowedModels);
2839
+ return requestRaw("/audio/speech", { body, method: "POST" });
2840
+ },
2841
+ transcribe: (body) => {
2842
+ if (body instanceof FormData) {
2843
+ const model = body.get("model");
2844
+ if (typeof model !== "string")
2845
+ throw new Error("OpenRouter transcription FormData requires model");
2846
+ assertAllowedModel(model, allowedModels);
2847
+ } else {
2848
+ assertAllowedModel(body.model, allowedModels);
2849
+ }
2850
+ return request("/audio/transcriptions", {
2851
+ body,
2852
+ method: "POST"
2853
+ });
2854
+ },
2855
+ uploadFile: (file, options = {}) => {
2856
+ const body = new FormData;
2857
+ if (options.filename)
2858
+ body.append("file", file, options.filename);
2859
+ else
2860
+ body.append("file", file);
2861
+ return request("/files", {
2862
+ body,
2863
+ method: "POST",
2864
+ query: { workspace_id: options.workspaceId }
2865
+ });
2866
+ }
2867
+ };
2868
+ };
2869
+
2870
+ // src/ai/providers/openrouter.ts
2871
+ var DEFAULT_BASE_URL7 = "https://openrouter.ai/api";
2872
+ var MAX_APP_CATEGORIES = 2;
2873
+ var withoutLatestPrefix2 = (model) => model.startsWith("~") ? model.slice(1) : model;
2874
+ var modelForOpenAICapabilities = (model) => {
2875
+ const normalized = withoutLatestPrefix2(model);
2876
+ return normalized.startsWith("openai/") ? normalized.slice("openai/".length) : normalized;
2877
+ };
2878
+ var modelMatchesRule = (model, rule) => {
2879
+ const normalizedModel = withoutLatestPrefix2(model);
2880
+ const normalizedRule = withoutLatestPrefix2(rule);
2881
+ if (normalizedRule.endsWith("/*")) {
2882
+ return normalizedModel.startsWith(normalizedRule.slice(0, -1));
2883
+ }
2884
+ return normalizedModel === normalizedRule;
2885
+ };
2886
+ var providerMatchesRule = (provider, rule) => provider === rule || provider.startsWith(`${rule}/`);
2887
+ var assertNonEmptyPolicy = (label, value) => {
2888
+ if (value && value.length === 0) {
2889
+ throw new Error(`openrouter() ${label} must not be empty`);
2890
+ }
2891
+ };
2892
+ var assertRoutingPolicy = (config2) => {
2893
+ assertNonEmptyPolicy("allowedProviders", config2.allowedProviders);
2894
+ assertNonEmptyPolicy("routing.only", config2.routing?.only);
2895
+ assertNonEmptyPolicy("allowedPresets", config2.allowedPresets);
2896
+ if (config2.appCategories && config2.appCategories.length > MAX_APP_CATEGORIES) {
2897
+ throw new Error("openrouter() appCategories supports at most 2 entries");
2898
+ }
2899
+ if (!config2.allowedProviders)
2900
+ return;
2901
+ const selected = [
2902
+ ...config2.routing?.only ?? [],
2903
+ ...config2.routing?.order ?? []
2904
+ ];
2905
+ const denied = selected.find((provider) => !config2.allowedProviders.some((rule) => providerMatchesRule(provider, rule)));
2906
+ if (denied) {
2907
+ throw new Error(`openrouter() provider "${denied}" is outside allowedProviders`);
2908
+ }
2909
+ };
2910
+ var assertRequestRoutingPolicy = (routing, allowedProviders) => {
2911
+ assertRoutingPolicy({
2912
+ allowedProviders,
2913
+ apiKey: "policy-validation",
2914
+ routing
2915
+ });
2916
+ };
2917
+ var mapRouting = (routing, allowedProviders) => {
2918
+ const only = routing?.only ?? allowedProviders;
2919
+ const wire = {};
2920
+ if (typeof routing?.allowFallbacks === "boolean")
2921
+ wire.allow_fallbacks = routing.allowFallbacks;
2922
+ if (routing?.dataCollection)
2923
+ wire.data_collection = routing.dataCollection;
2924
+ if (typeof routing?.enforceDistillableText === "boolean")
2925
+ wire.enforce_distillable_text = routing.enforceDistillableText;
2926
+ if (routing?.ignore)
2927
+ wire.ignore = [...routing.ignore];
2928
+ if (routing?.maxPrice)
2929
+ wire.max_price = { ...routing.maxPrice };
2930
+ if (only)
2931
+ wire.only = [...only];
2932
+ if (routing?.order)
2933
+ wire.order = [...routing.order];
2934
+ if (routing?.preferredMaxLatency !== undefined)
2935
+ wire.preferred_max_latency = routing.preferredMaxLatency;
2936
+ if (routing?.preferredMinThroughput !== undefined)
2937
+ wire.preferred_min_throughput = routing.preferredMinThroughput;
2938
+ if (routing?.quantizations)
2939
+ wire.quantizations = [...routing.quantizations];
2940
+ if (typeof routing?.requireParameters === "boolean")
2941
+ wire.require_parameters = routing.requireParameters;
2942
+ if (routing?.sort)
2943
+ wire.sort = routing.sort;
2944
+ if (typeof routing?.zdr === "boolean")
2945
+ wire.zdr = routing.zdr;
2946
+ return wire;
2947
+ };
2948
+ var requestOptionsFor = (params, defaults) => {
2949
+ const supplied = params.providerOptions?.openrouter;
2950
+ if (supplied !== undefined && (typeof supplied !== "object" || !supplied)) {
2951
+ throw new Error("providerOptions.openrouter must be an object");
2952
+ }
2953
+ return {
2954
+ ...defaults,
2955
+ ...supplied
2956
+ };
2957
+ };
2958
+ var resolveAttributionHeaders = async (config2, params) => {
2959
+ const supplied = typeof config2.headers === "function" ? await config2.headers() : config2.headers ?? {};
2960
+ const headers = new Headers(supplied);
2961
+ if (config2.appUrl)
2962
+ headers.set("HTTP-Referer", config2.appUrl);
2963
+ if (config2.appName)
2964
+ headers.set("X-OpenRouter-Title", config2.appName);
2965
+ if (config2.appCategories?.length) {
2966
+ headers.set("X-OpenRouter-Categories", config2.appCategories.join(","));
2967
+ }
2968
+ const options = requestOptionsFor(params, config2.requestOptions);
2969
+ if (options.routerMetadata ?? true)
2970
+ headers.set("X-OpenRouter-Metadata", "enabled");
2971
+ if (options.sessionId)
2972
+ headers.set("X-Session-Id", options.sessionId);
2973
+ if (options.responseCache) {
2974
+ headers.set("X-OpenRouter-Cache", options.responseCache.enabled ? "true" : "false");
2975
+ if (options.responseCache.ttlSeconds !== undefined)
2976
+ headers.set("X-OpenRouter-Cache-TTL", String(options.responseCache.ttlSeconds));
2977
+ if (options.responseCache.clear)
2978
+ headers.set("X-OpenRouter-Cache-Clear", "true");
2979
+ }
2980
+ return headers;
2981
+ };
2982
+ var SECURITY_SENSITIVE_EXTRA_BODY_FIELDS = new Set([
2983
+ "messages",
2984
+ "model",
2985
+ "models",
2986
+ "plugins",
2987
+ "preset",
2988
+ "provider",
2989
+ "stream",
2990
+ "tools"
2991
+ ]);
2992
+ var assertAllowedPreset = (preset, allowedPresets) => {
2993
+ if (!preset)
2994
+ return;
2995
+ if (allowedPresets?.includes(preset))
2996
+ return;
2997
+ throw new Error(`OpenRouter preset "${preset}" is not allowed`);
2998
+ };
2999
+ var assertIndirectModels = (value, allowedModels, key = "") => {
3000
+ if (key === "model" && typeof value === "string")
3001
+ assertAllowedModel2(value, allowedModels);
3002
+ if ((key === "models" || key === "analysis_models") && Array.isArray(value)) {
3003
+ for (const model of value) {
3004
+ if (typeof model === "string")
3005
+ assertAllowedModel2(model, allowedModels);
3006
+ }
3007
+ }
3008
+ if (Array.isArray(value)) {
3009
+ for (const item of value)
3010
+ assertIndirectModels(item, allowedModels);
3011
+ } else if (value && typeof value === "object") {
3012
+ for (const [childKey, child] of Object.entries(value))
3013
+ assertIndirectModels(child, allowedModels, childKey);
3014
+ }
3015
+ };
3016
+ var assertRequestOptions = (options, allowedModels, allowedPresets, allowedProviders) => {
3017
+ assertAllowedPreset(options.preset, allowedPresets);
3018
+ assertRequestRoutingPolicy(options.routing, allowedProviders);
3019
+ if (options.sessionId && options.sessionId.length > 256)
3020
+ throw new Error("OpenRouter sessionId must be at most 256 characters");
3021
+ const ttl = options.responseCache?.ttlSeconds;
3022
+ if (ttl !== undefined && (!Number.isInteger(ttl) || ttl < 1 || ttl > 86400))
3023
+ throw new Error("OpenRouter response-cache TTL must be 1-86400 seconds");
3024
+ if (options.responseCache?.clear && !options.responseCache.enabled)
3025
+ throw new Error("OpenRouter cache clear requires response caching enabled");
3026
+ if (options.fallbackModels) {
3027
+ if (options.fallbackModels.length === 0)
3028
+ throw new Error("OpenRouter fallbackModels must not be empty");
3029
+ for (const model of options.fallbackModels)
3030
+ assertAllowedModel2(model, allowedModels);
3031
+ }
3032
+ assertIndirectModels(options.serverTools, allowedModels);
3033
+ assertIndirectModels(options.messagesTools, allowedModels);
3034
+ assertIndirectModels(options.plugins, allowedModels);
3035
+ if (options.extraBody) {
3036
+ const unsafe = Object.keys(options.extraBody).find((key) => SECURITY_SENSITIVE_EXTRA_BODY_FIELDS.has(key));
3037
+ if (unsafe)
3038
+ throw new Error(`OpenRouter extraBody cannot override "${unsafe}"`);
3039
+ }
3040
+ };
3041
+ var assertAllowedModel2 = (model, allowedModels) => {
3042
+ if (!allowedModels)
3043
+ return;
3044
+ if (allowedModels.some((rule) => modelMatchesRule(model, rule)))
3045
+ return;
3046
+ throw new Error(`OpenRouter model "${model}" is not allowed`);
3047
+ };
3048
+ var snapshotPolicy = (config2) => ({
3049
+ allowedModels: config2.allowedModels ? [...config2.allowedModels] : undefined,
3050
+ allowedPresets: config2.allowedPresets ? [...config2.allowedPresets] : undefined
3051
+ });
3052
+ var transformOpenRouterRequest = (config2, allowedModels, allowedPresets, body, params, skin = "openai") => {
3053
+ const options = requestOptionsFor(params, config2.requestOptions);
3054
+ assertRequestOptions(options, allowedModels, allowedPresets, config2.allowedProviders);
3055
+ const transformed = { ...body, ...options.extraBody };
3056
+ if (options.audioOutput) {
3057
+ transformed.audio = options.audioOutput;
3058
+ transformed.modalities = ["text", "audio"];
3059
+ }
3060
+ if (skin === "openai") {
3061
+ const requestedReasoning = {};
3062
+ if (params.reasoning?.budgetTokens !== undefined) {
3063
+ requestedReasoning.max_tokens = params.reasoning.budgetTokens;
3064
+ delete transformed.reasoning_effort;
3065
+ } else if (params.reasoning?.effort) {
3066
+ requestedReasoning.effort = params.reasoning.effort;
3067
+ delete transformed.reasoning_effort;
3068
+ }
3069
+ if (options.reasoning) {
3070
+ Object.assign(requestedReasoning, options.reasoning);
3071
+ if (options.reasoning.maxTokens !== undefined) {
3072
+ requestedReasoning.max_tokens = options.reasoning.maxTokens;
3073
+ delete requestedReasoning.maxTokens;
3074
+ }
3075
+ }
3076
+ if (Object.keys(requestedReasoning).length > 0)
3077
+ transformed.reasoning = requestedReasoning;
3078
+ }
3079
+ const automaticCacheControl = params.promptCaching === true || params.cacheSystemPrompt === true ? { type: "ephemeral" } : undefined;
3080
+ const cacheControl = options.cacheControl ?? automaticCacheControl;
3081
+ if (cacheControl)
3082
+ transformed.cache_control = cacheControl;
3083
+ if (options.promptCacheKey)
3084
+ transformed.prompt_cache_key = options.promptCacheKey;
3085
+ if (options.promptCacheOptions)
3086
+ transformed.prompt_cache_options = options.promptCacheOptions;
3087
+ const routing = mapRouting({ ...config2.routing, ...options.routing }, config2.allowedProviders);
3088
+ if (Object.keys(routing).length > 0)
3089
+ transformed.provider = routing;
3090
+ if (options.fallbackModels)
3091
+ transformed.models = [...options.fallbackModels];
3092
+ if (options.includeReasoning !== undefined)
3093
+ transformed.include_reasoning = options.includeReasoning;
3094
+ if (options.maxToolCalls !== undefined)
3095
+ transformed.max_tool_calls = options.maxToolCalls;
3096
+ if (options.plugins)
3097
+ transformed.plugins = [...options.plugins];
3098
+ if (options.preset)
3099
+ transformed.preset = options.preset;
3100
+ if (options.serverTools) {
3101
+ transformed.tools = [
3102
+ ...Array.isArray(transformed.tools) ? transformed.tools : [],
3103
+ ...options.serverTools
3104
+ ];
3105
+ }
3106
+ if (options.messagesTools) {
3107
+ if (skin !== "messages")
3108
+ throw new Error("OpenRouter messagesTools requires openrouterMessages()");
3109
+ transformed.tools = [
3110
+ ...Array.isArray(transformed.tools) ? transformed.tools : [],
3111
+ ...options.messagesTools
3112
+ ];
3113
+ }
3114
+ if (options.serviceTier)
3115
+ transformed.service_tier = options.serviceTier;
3116
+ if (options.sessionId)
3117
+ transformed.session_id = options.sessionId;
3118
+ if (options.stopServerToolsWhen)
3119
+ transformed.stop_server_tools_when = options.stopServerToolsWhen;
3120
+ if (options.trace)
3121
+ transformed.trace = options.trace;
3122
+ if (options.transforms)
3123
+ transformed.transforms = [...options.transforms];
3124
+ if (options.user)
3125
+ transformed.user = options.user;
3126
+ if (options.verbosity)
3127
+ transformed.verbosity = options.verbosity;
3128
+ return transformed;
3129
+ };
3130
+ var withOpenRouterPolicy = (provider, allowedModels, allowedPresets) => ({
3131
+ stream: (params) => {
3132
+ if (params.model.startsWith("@preset/")) {
3133
+ assertAllowedPreset(params.model.slice("@preset/".length), allowedPresets);
3134
+ } else {
3135
+ const presetSeparator = params.model.indexOf("@preset/");
3136
+ if (presetSeparator >= 0) {
3137
+ assertAllowedModel2(params.model.slice(0, presetSeparator), allowedModels);
3138
+ assertAllowedPreset(params.model.slice(presetSeparator + "@preset/".length), allowedPresets);
3139
+ } else {
3140
+ assertAllowedModel2(params.model, allowedModels);
3141
+ }
3142
+ }
3143
+ return provider.stream(params);
3144
+ }
3145
+ });
3146
+ var openrouter = (config2) => {
3147
+ assertRoutingPolicy(config2);
3148
+ const { allowedModels, allowedPresets } = snapshotPolicy(config2);
3149
+ const provider = openai({
3150
+ apiKey: config2.apiKey,
3151
+ baseUrl: config2.baseUrl ?? DEFAULT_BASE_URL7,
3152
+ fetch: config2.fetch,
3153
+ headers: (params) => resolveAttributionHeaders(config2, params),
3154
+ modelForCapabilities: modelForOpenAICapabilities,
3155
+ providerName: "openrouter",
3156
+ tokenSource: config2.tokenSource,
3157
+ transformRequestBody: (body, params) => transformOpenRouterRequest(config2, allowedModels, allowedPresets, body, params)
3158
+ });
3159
+ return withOpenRouterPolicy(provider, allowedModels, allowedPresets);
3160
+ };
3161
+ var openrouterResponses = (config2) => {
3162
+ assertRoutingPolicy(config2);
3163
+ const { allowedModels, allowedPresets } = snapshotPolicy(config2);
3164
+ const provider = openaiResponses({
3165
+ apiKey: config2.apiKey,
3166
+ baseUrl: config2.baseUrl ?? DEFAULT_BASE_URL7,
3167
+ fetch: config2.fetch,
3168
+ headers: (params) => resolveAttributionHeaders(config2, params),
3169
+ modelForCapabilities: modelForOpenAICapabilities,
3170
+ providerName: "openrouter",
3171
+ tokenSource: config2.tokenSource,
3172
+ transformRequestBody: (body, params) => transformOpenRouterRequest(config2, allowedModels, allowedPresets, body, params)
3173
+ });
3174
+ return withOpenRouterPolicy(provider, allowedModels, allowedPresets);
3175
+ };
3176
+ var openrouterMessages = (config2) => {
3177
+ assertRoutingPolicy(config2);
3178
+ const { allowedModels, allowedPresets } = snapshotPolicy(config2);
3179
+ const provider = anthropic({
3180
+ apiKey: config2.apiKey,
3181
+ authStyle: "bearer",
3182
+ baseUrl: config2.baseUrl ?? DEFAULT_BASE_URL7,
3183
+ fetch: config2.fetch,
3184
+ headers: (params) => resolveAttributionHeaders(config2, params),
3185
+ providerName: "openrouter",
3186
+ tokenSource: config2.tokenSource,
3187
+ transformRequestBody: (body, params) => transformOpenRouterRequest(config2, allowedModels, allowedPresets, body, params, "messages")
3188
+ });
3189
+ return withOpenRouterPolicy(provider, allowedModels, allowedPresets);
3190
+ };
3191
+
2231
3192
  // src/plugins/aiChat.ts
2232
3193
  import { Elysia } from "elysia";
2233
3194
 
@@ -2403,6 +3364,8 @@ var isValidAIServerMessage = (data) => {
2403
3364
  return "name" in data && "status" in data && "messageId" in data && "conversationId" in data;
2404
3365
  case "image":
2405
3366
  return "data" in data && typeof data.data === "string" && "format" in data && typeof data.format === "string" && "isPartial" in data && typeof data.isPartial === "boolean" && "messageId" in data && "conversationId" in data;
3367
+ case "audio":
3368
+ return "data" in data && typeof data.data === "string" && "format" in data && typeof data.format === "string" && "messageId" in data && "conversationId" in data;
2406
3369
  case "complete":
2407
3370
  return "messageId" in data && "conversationId" in data;
2408
3371
  case "turn_queued":
@@ -2502,6 +3465,15 @@ var sendImageMessage = async (socket, chunk, messageId, conversationId) => sendM
2502
3465
  revisedPrompt: chunk.revisedPrompt,
2503
3466
  type: "image"
2504
3467
  });
3468
+ var sendAudioMessage = async (socket, chunk, messageId, conversationId) => sendMessage(socket, {
3469
+ audioId: chunk.audioId,
3470
+ conversationId,
3471
+ data: chunk.data,
3472
+ format: chunk.format,
3473
+ messageId,
3474
+ transcript: chunk.transcript,
3475
+ type: "audio"
3476
+ });
2505
3477
  var sendToolRunning = async (socket, toolName, toolInput, messageId, conversationId) => sendMessage(socket, {
2506
3478
  conversationId,
2507
3479
  input: toolInput,
@@ -2595,6 +3567,15 @@ var processToolChunk = (chunk, state, options, socket, messageId, conversationId
2595
3567
  revisedPrompt: chunk.revisedPrompt
2596
3568
  });
2597
3569
  break;
3570
+ case "audio":
3571
+ sendAudioMessage(socket, chunk, messageId, conversationId);
3572
+ options.onAudio?.({
3573
+ audioId: chunk.audioId,
3574
+ data: chunk.data,
3575
+ format: chunk.format,
3576
+ transcript: chunk.transcript
3577
+ });
3578
+ break;
2598
3579
  case "tool_use":
2599
3580
  flushThinking(state);
2600
3581
  handleToolChunkToolUse(chunk, state);
@@ -2602,6 +3583,7 @@ var processToolChunk = (chunk, state, options, socket, messageId, conversationId
2602
3583
  id: chunk.id,
2603
3584
  input: typeof chunk.input === "object" && chunk.input !== null ? chunk.input : {},
2604
3585
  name: chunk.name,
3586
+ providerData: chunk.providerData,
2605
3587
  type: "tool_use"
2606
3588
  });
2607
3589
  hitAnotherTool = true;
@@ -2644,6 +3626,7 @@ var processToolTurn = async (socket, options, state, messageId, conversationId,
2644
3626
  messages: state.currentMessages,
2645
3627
  model: options.model,
2646
3628
  promptCaching: options.promptCaching,
3629
+ providerOptions: options.providerOptions,
2647
3630
  reasoning: options.reasoning,
2648
3631
  signal,
2649
3632
  systemPrompt: options.systemPrompt,
@@ -2730,6 +3713,7 @@ var processStream = async (socket, options, messages, messageId, conversationId,
2730
3713
  messages,
2731
3714
  model: options.model,
2732
3715
  promptCaching: options.promptCaching,
3716
+ providerOptions: options.providerOptions,
2733
3717
  reasoning: options.reasoning,
2734
3718
  signal,
2735
3719
  systemPrompt: options.systemPrompt,
@@ -2787,6 +3771,15 @@ var consumeStreamChunk = async (chunk, options, socket, state, messageId, conver
2787
3771
  revisedPrompt: chunk.revisedPrompt
2788
3772
  });
2789
3773
  break;
3774
+ case "audio":
3775
+ await sendAudioMessage(socket, chunk, messageId, conversationId);
3776
+ options.onAudio?.({
3777
+ audioId: chunk.audioId,
3778
+ data: chunk.data,
3779
+ format: chunk.format,
3780
+ transcript: chunk.transcript
3781
+ });
3782
+ break;
2790
3783
  case "tool_use":
2791
3784
  flushStreamThinking(state);
2792
3785
  state.pendingToolCalls.push({
@@ -2798,6 +3791,7 @@ var consumeStreamChunk = async (chunk, options, socket, state, messageId, conver
2798
3791
  id: chunk.id,
2799
3792
  input: typeof chunk.input === "object" && chunk.input !== null ? chunk.input : {},
2800
3793
  name: chunk.name,
3794
+ providerData: chunk.providerData,
2801
3795
  type: "tool_use"
2802
3796
  });
2803
3797
  break;
@@ -2898,6 +3892,10 @@ var serializeToolCall2 = (name, input) => `${name}:${JSON.stringify(input)}`;
2898
3892
  var contentEvent = (options, renderers, delta, full) => options.structuredEvents ? { data: JSON.stringify({ delta, full }), event: "content" } : { data: renderers.chunk(delta, full), event: "content" };
2899
3893
  var thinkingEvent = (options, renderers, text) => options.structuredEvents ? { data: JSON.stringify({ text }), event: "thinking" } : { data: renderers.thinking(text), event: "thinking" };
2900
3894
  var imageEvent = (options, renderers, data, format, revisedPrompt) => options.structuredEvents ? { data: JSON.stringify({ data, format, revisedPrompt }), event: "images" } : { data: renderers.image(data, format, revisedPrompt), event: "images" };
3895
+ var audioEvent = (data, format, transcript, audioId) => ({
3896
+ data: JSON.stringify({ audioId, data, format, transcript }),
3897
+ event: "audio"
3898
+ });
2901
3899
  var completeEvent = (options, renderers, fullResponse, usage, durationMs) => {
2902
3900
  options.onComplete?.(fullResponse, usage);
2903
3901
  return options.structuredEvents ? {
@@ -2967,6 +3965,15 @@ var processImageChunk = function* (chunk, renderers, options) {
2967
3965
  revisedPrompt: chunk.revisedPrompt
2968
3966
  });
2969
3967
  };
3968
+ var processAudioChunk = function* (chunk, options) {
3969
+ yield audioEvent(chunk.data, chunk.format, chunk.transcript, chunk.audioId);
3970
+ options.onAudio?.({
3971
+ audioId: chunk.audioId,
3972
+ data: chunk.data,
3973
+ format: chunk.format,
3974
+ transcript: chunk.transcript
3975
+ });
3976
+ };
2970
3977
  var processToolUseChunk = (chunk, chunkState) => {
2971
3978
  maybeFlushThinking(chunkState);
2972
3979
  chunkState.pendingToolCalls.push({
@@ -2978,6 +3985,7 @@ var processToolUseChunk = (chunk, chunkState) => {
2978
3985
  id: chunk.id,
2979
3986
  input: typeof chunk.input === "object" && chunk.input !== null ? chunk.input : {},
2980
3987
  name: chunk.name,
3988
+ providerData: chunk.providerData,
2981
3989
  type: "tool_use"
2982
3990
  });
2983
3991
  };
@@ -2992,6 +4000,9 @@ var processChunk2 = function* (chunk, chunkState, renderers, options, fullRespon
2992
4000
  case "image":
2993
4001
  yield* processImageChunk(chunk, renderers, options);
2994
4002
  break;
4003
+ case "audio":
4004
+ yield* processAudioChunk(chunk, options);
4005
+ break;
2995
4006
  case "tool_use":
2996
4007
  processToolUseChunk(chunk, chunkState);
2997
4008
  break;
@@ -3111,6 +4122,7 @@ var streamTurns = async function* (options, renderers, messages, signal, startTi
3111
4122
  messages: turnState.currentMessages,
3112
4123
  model: options.model,
3113
4124
  promptCaching: options.promptCaching,
4125
+ providerOptions: options.providerOptions,
3114
4126
  reasoning: options.reasoning,
3115
4127
  signal,
3116
4128
  systemPrompt: options.systemPrompt,
@@ -3123,6 +4135,13 @@ var streamTurns = async function* (options, renderers, messages, signal, startTi
3123
4135
  aggregateUsage.outputTokens += chunkState.usage?.outputTokens ?? 0;
3124
4136
  aggregateUsage.cacheReadInputTokens = (aggregateUsage.cacheReadInputTokens ?? 0) + (chunkState.usage?.cacheReadInputTokens ?? 0);
3125
4137
  aggregateUsage.cacheWriteInputTokens = (aggregateUsage.cacheWriteInputTokens ?? 0) + (chunkState.usage?.cacheWriteInputTokens ?? 0);
4138
+ aggregateUsage.costCredits = (aggregateUsage.costCredits ?? 0) + (chunkState.usage?.costCredits ?? 0);
4139
+ aggregateUsage.reasoningTokens = (aggregateUsage.reasoningTokens ?? 0) + (chunkState.usage?.reasoningTokens ?? 0);
4140
+ for (const [key, value] of Object.entries(chunkState.usage?.serverToolUse ?? {})) {
4141
+ aggregateUsage.serverToolUse ??= {};
4142
+ aggregateUsage.serverToolUse[key] = (aggregateUsage.serverToolUse[key] ?? 0) + value;
4143
+ }
4144
+ aggregateUsage.upstreamInferenceCostCredits = (aggregateUsage.upstreamInferenceCostCredits ?? 0) + (chunkState.usage?.upstreamInferenceCostCredits ?? 0);
3126
4145
  const runningTotalTokens = aggregateUsage.inputTokens + aggregateUsage.outputTokens;
3127
4146
  if (chunkState.stopReason === "max_tokens") {
3128
4147
  finishReason = "max_tokens";
@@ -3606,6 +4625,7 @@ var generateAI = async (options) => {
3606
4625
  messages: options.messages,
3607
4626
  model: options.model,
3608
4627
  promptCaching: options.promptCaching,
4628
+ providerOptions: options.providerOptions,
3609
4629
  reasoning: options.reasoning,
3610
4630
  responseFormat: options.responseFormat,
3611
4631
  signal: options.signal,
@@ -3618,17 +4638,22 @@ var generateAI = async (options) => {
3618
4638
  });
3619
4639
  let text = "";
3620
4640
  const toolCalls = [];
4641
+ const citations = [];
3621
4642
  let usage;
4643
+ let metadata;
3622
4644
  for await (const chunk of stream) {
3623
4645
  if (chunk.type === "text") {
3624
4646
  text += chunk.content;
3625
4647
  } else if (chunk.type === "tool_use") {
3626
4648
  toolCalls.push({ id: chunk.id, input: chunk.input, name: chunk.name });
4649
+ } else if (chunk.type === "citation") {
4650
+ citations.push(chunk);
3627
4651
  } else if (chunk.type === "done") {
3628
4652
  usage = chunk.usage;
4653
+ metadata = chunk.metadata;
3629
4654
  }
3630
4655
  }
3631
- return { text, toolCalls, usage };
4656
+ return { citations, metadata, text, toolCalls, usage };
3632
4657
  };
3633
4658
  var DEFAULT_TOOL_MAX_TURNS = 6;
3634
4659
  var mergeUsage = (left, right) => {
@@ -3637,11 +4662,18 @@ var mergeUsage = (left, right) => {
3637
4662
  if (!right)
3638
4663
  return left;
3639
4664
  const add = (a, b) => a === undefined && b === undefined ? undefined : (a ?? 0) + (b ?? 0);
4665
+ const serverToolUse = { ...left.serverToolUse };
4666
+ for (const [key, value] of Object.entries(right.serverToolUse ?? {}))
4667
+ serverToolUse[key] = (serverToolUse[key] ?? 0) + value;
3640
4668
  return {
3641
4669
  cacheReadInputTokens: add(left.cacheReadInputTokens, right.cacheReadInputTokens),
3642
4670
  cacheWriteInputTokens: add(left.cacheWriteInputTokens, right.cacheWriteInputTokens),
4671
+ costCredits: add(left.costCredits, right.costCredits),
3643
4672
  inputTokens: (left.inputTokens ?? 0) + (right.inputTokens ?? 0),
3644
- outputTokens: (left.outputTokens ?? 0) + (right.outputTokens ?? 0)
4673
+ outputTokens: (left.outputTokens ?? 0) + (right.outputTokens ?? 0),
4674
+ reasoningTokens: add(left.reasoningTokens, right.reasoningTokens),
4675
+ serverToolUse: Object.keys(serverToolUse).length > 0 ? serverToolUse : undefined,
4676
+ upstreamInferenceCostCredits: add(left.upstreamInferenceCostCredits, right.upstreamInferenceCostCredits)
3645
4677
  };
3646
4678
  };
3647
4679
  var toProviderTools = (tools) => Object.entries(tools).map(([name, definition]) => ({
@@ -3825,6 +4857,7 @@ var streamAIWithTools = async function* (options) {
3825
4857
  messages,
3826
4858
  model: base.model,
3827
4859
  promptCaching: base.promptCaching,
4860
+ providerOptions: base.providerOptions,
3828
4861
  reasoning: base.reasoning,
3829
4862
  signal: base.signal,
3830
4863
  stopSequences: base.stopSequences,
@@ -3853,6 +4886,14 @@ var streamAIWithTools = async function* (options) {
3853
4886
  fullText += chunk.content;
3854
4887
  pushText(blocks, chunk.content);
3855
4888
  yield { content: chunk.content, type: "text" };
4889
+ } else if (chunk.type === "audio") {
4890
+ yield {
4891
+ audioId: chunk.audioId,
4892
+ data: chunk.data,
4893
+ format: chunk.format,
4894
+ transcript: chunk.transcript,
4895
+ type: "audio"
4896
+ };
3856
4897
  } else if (chunk.type === "tool_use") {
3857
4898
  thinking = flushThinking3(blocks, thinking);
3858
4899
  pending.push({ id: chunk.id, input: chunk.input, name: chunk.name });
@@ -3860,8 +4901,16 @@ var streamAIWithTools = async function* (options) {
3860
4901
  id: chunk.id,
3861
4902
  input: chunk.input && typeof chunk.input === "object" ? chunk.input : {},
3862
4903
  name: chunk.name,
4904
+ providerData: chunk.providerData,
3863
4905
  type: "tool_use"
3864
4906
  });
4907
+ } else if (chunk.type === "provider_event") {
4908
+ thinking = flushThinking3(blocks, thinking);
4909
+ blocks.push({
4910
+ data: chunk.data,
4911
+ provider: chunk.provider,
4912
+ type: "provider_data"
4913
+ });
3865
4914
  } else if (chunk.type === "done") {
3866
4915
  thinking = flushThinking3(blocks, thinking);
3867
4916
  turnUsage = chunk.usage;
@@ -5406,6 +6455,16 @@ var serverMessageToAction = (message) => {
5406
6455
  revisedPrompt: message.revisedPrompt,
5407
6456
  type: "image"
5408
6457
  };
6458
+ case "audio":
6459
+ return {
6460
+ audioId: message.audioId,
6461
+ conversationId: message.conversationId,
6462
+ data: message.data,
6463
+ format: message.format,
6464
+ messageId: message.messageId,
6465
+ transcript: message.transcript,
6466
+ type: "audio"
6467
+ };
5409
6468
  case "complete":
5410
6469
  return {
5411
6470
  conversationId: message.conversationId,
@@ -5808,6 +6867,20 @@ var handleImage = (state, action) => {
5808
6867
  });
5809
6868
  conversation.messages = [...conversation.messages];
5810
6869
  };
6870
+ var handleAudio = (state, action) => {
6871
+ const conversation = getOrCreate(state, action.conversationId);
6872
+ const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
6873
+ message.audio = [
6874
+ ...message.audio ?? [],
6875
+ {
6876
+ audioId: action.audioId,
6877
+ data: action.data,
6878
+ format: action.format,
6879
+ transcript: action.transcript
6880
+ }
6881
+ ];
6882
+ conversation.messages = [...conversation.messages];
6883
+ };
5811
6884
  var handleComplete = (state, action) => {
5812
6885
  const conversation = state.conversations.get(action.conversationId);
5813
6886
  if (conversation) {
@@ -5878,6 +6951,9 @@ var applyAction = (state, action) => {
5878
6951
  case "image":
5879
6952
  handleImage(state, action);
5880
6953
  break;
6954
+ case "audio":
6955
+ handleAudio(state, action);
6956
+ break;
5881
6957
  case "complete":
5882
6958
  handleComplete(state, action);
5883
6959
  break;
@@ -6197,6 +7273,7 @@ var startProviderStatusMonitor = (options) => {
6197
7273
  export {
6198
7274
  xai,
6199
7275
  withResilience,
7276
+ verifyOpenRouterWebhookSignature,
6200
7277
  tableCard,
6201
7278
  streamAIWithTools,
6202
7279
  streamAIToSSE,
@@ -6223,9 +7300,13 @@ export {
6223
7300
  parseChoiceSpec,
6224
7301
  parseChartSpec,
6225
7302
  parseAIMessage,
7303
+ openrouterResponses,
7304
+ openrouterMessages,
7305
+ openrouter,
6226
7306
  openaiResponses,
6227
7307
  openaiCompatible,
6228
7308
  openai,
7309
+ openRouterModelMatchesRule,
6229
7310
  ollama,
6230
7311
  moonshot,
6231
7312
  mistralai,
@@ -6245,6 +7326,7 @@ export {
6245
7326
  createUiCards,
6246
7327
  createSyncConversationStore,
6247
7328
  createProviderProxyResponse,
7329
+ createOpenRouterClient,
6248
7330
  createOAuth2ClientCredentialsTokenSource,
6249
7331
  createMemoryStore,
6250
7332
  createConversationManager,
@@ -6282,5 +7364,5 @@ export {
6282
7364
  BUILTIN_UI_CARDS
6283
7365
  };
6284
7366
 
6285
- //# debugId=8DDC9883DEF6AEEE64756E2164756E21
7367
+ //# debugId=3B5ACA11D1118B9564756E2164756E21
6286
7368
  //# sourceMappingURL=index.js.map