@absolutejs/ai 0.0.49 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,
@@ -456,7 +457,7 @@ var mapContentBlockToOpenAI = (block) => {
456
457
  if (block.type === "image") {
457
458
  return {
458
459
  image_url: {
459
- url: `data:${block.source.media_type};base64,${block.source.data}`
460
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
460
461
  },
461
462
  type: "image_url"
462
463
  };
@@ -464,12 +465,26 @@ var mapContentBlockToOpenAI = (block) => {
464
465
  if (block.type === "document") {
465
466
  return {
466
467
  file: {
467
- file_data: `data:${block.source.media_type};base64,${block.source.data}`,
468
+ file_data: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`,
468
469
  filename: block.name ?? "document.pdf"
469
470
  },
470
471
  type: "file"
471
472
  };
472
473
  }
474
+ if (block.type === "audio") {
475
+ return {
476
+ input_audio: { data: block.source.data, format: block.source.format },
477
+ type: "input_audio"
478
+ };
479
+ }
480
+ if (block.type === "video") {
481
+ return {
482
+ type: "video_url",
483
+ video_url: {
484
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
485
+ }
486
+ };
487
+ }
473
488
  if (block.type === "text") {
474
489
  return { text: block.content, type: "text" };
475
490
  }
@@ -479,13 +494,13 @@ var mapOpenAIContent = (msg) => {
479
494
  if (typeof msg.content === "string") {
480
495
  return msg.content;
481
496
  }
482
- const hasMedia = msg.content.some((block) => block.type === "image" || block.type === "document");
497
+ const hasMedia = msg.content.some((block) => block.type === "image" || block.type === "document" || block.type === "audio" || block.type === "video");
483
498
  if (!hasMedia) {
484
499
  return null;
485
500
  }
486
501
  return msg.content.map(mapContentBlockToOpenAI).filter((mapped) => mapped !== null);
487
502
  };
488
- var buildRequestBody = (params) => {
503
+ var buildRequestBody = (params, capabilityModel = params.model) => {
489
504
  const messages = convertToolResultMessages(params.messages.map((msg) => ({
490
505
  content: mapOpenAIContent(msg),
491
506
  role: msg.role
@@ -510,12 +525,12 @@ var buildRequestBody = (params) => {
510
525
  body.parallel_tool_calls = params.parallelToolCalls;
511
526
  }
512
527
  }
513
- if (isOpenAIReasoningModel(params.model)) {
528
+ if (isOpenAIReasoningModel(capabilityModel)) {
514
529
  if (typeof params.maxTokens === "number") {
515
530
  body.max_completion_tokens = params.maxTokens;
516
531
  }
517
532
  if (params.reasoning) {
518
- const effort = openaiEffortValue(params.model, params.reasoning);
533
+ const effort = openaiEffortValue(capabilityModel, params.reasoning);
519
534
  if (effort)
520
535
  body.reasoning_effort = effort;
521
536
  }
@@ -576,8 +591,12 @@ var extractUsage = (parsedUsage) => {
576
591
  const cached = parsedUsage.cached_tokens ?? 0;
577
592
  return {
578
593
  cacheReadInputTokens: cached,
594
+ cacheWriteInputTokens: parsedUsage.cache_write_tokens || undefined,
595
+ costCredits: parsedUsage.cost,
579
596
  inputTokens: Math.max(0, prompt - cached),
580
- outputTokens: parsedUsage.completion_tokens ?? 0
597
+ outputTokens: parsedUsage.completion_tokens ?? 0,
598
+ reasoningTokens: parsedUsage.reasoning_tokens || undefined,
599
+ upstreamInferenceCostCredits: parsedUsage.upstream_inference_cost || undefined
581
600
  };
582
601
  };
583
602
  var resolveToolCallIndex = (toolCall) => {
@@ -632,6 +651,35 @@ var processDelta = function* (delta, pendingToolCalls) {
632
651
  if (isRecordArray(delta.tool_calls)) {
633
652
  processToolCallDeltas(delta.tool_calls, pendingToolCalls);
634
653
  }
654
+ if (Array.isArray(delta.annotations)) {
655
+ for (const annotation of delta.annotations) {
656
+ if (!isRecord(annotation) || annotation.type !== "url_citation")
657
+ continue;
658
+ const citation = isRecord(annotation.url_citation) ? annotation.url_citation : annotation;
659
+ if (typeof citation.url !== "string")
660
+ continue;
661
+ yield {
662
+ content: typeof citation.content === "string" ? citation.content : undefined,
663
+ endIndex: typeof citation.end_index === "number" ? citation.end_index : undefined,
664
+ startIndex: typeof citation.start_index === "number" ? citation.start_index : undefined,
665
+ title: typeof citation.title === "string" ? citation.title : undefined,
666
+ type: "citation",
667
+ url: citation.url
668
+ };
669
+ }
670
+ }
671
+ };
672
+ var narrowResponseMetadata = (parsed) => {
673
+ const providerMetadata = isRecord(parsed.openrouter_metadata) ? parsed.openrouter_metadata : undefined;
674
+ const generationId = typeof parsed.id === "string" ? parsed.id : undefined;
675
+ const model = typeof parsed.model === "string" ? parsed.model : undefined;
676
+ const serviceTier = typeof parsed.service_tier === "string" ? parsed.service_tier : undefined;
677
+ const selected = providerMetadata && isRecord(providerMetadata.endpoints) ? providerMetadata.endpoints.available : undefined;
678
+ const selectedEndpoint = Array.isArray(selected) ? selected.find((entry) => isRecord(entry) && entry.selected === true) : undefined;
679
+ const provider = isRecord(selectedEndpoint) && typeof selectedEndpoint.provider === "string" ? selectedEndpoint.provider : undefined;
680
+ if (!providerMetadata && !generationId && !model && !serviceTier)
681
+ return;
682
+ return { generationId, model, provider, providerMetadata, serviceTier };
635
683
  };
636
684
  var processChoice = function* (choice, pendingToolCalls) {
637
685
  const delta = isRecord(choice.delta) ? choice.delta : null;
@@ -650,13 +698,25 @@ var narrowUsageRecord = (parsed) => {
650
698
  const promptTokens = typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0;
651
699
  const completionTokens = typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0;
652
700
  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({
701
+ const cacheWriteTokens = isRecord(usage.prompt_tokens_details) && typeof usage.prompt_tokens_details.cache_write_tokens === "number" ? usage.prompt_tokens_details.cache_write_tokens : 0;
702
+ const reasoningTokens = isRecord(usage.completion_tokens_details) && typeof usage.completion_tokens_details.reasoning_tokens === "number" ? usage.completion_tokens_details.reasoning_tokens : 0;
703
+ const cost = typeof usage.cost === "number" ? usage.cost : undefined;
704
+ const upstreamInferenceCost = isRecord(usage.cost_details) && typeof usage.cost_details.upstream_inference_cost === "number" ? usage.cost_details.upstream_inference_cost : undefined;
705
+ const normalized = extractUsage({
706
+ cache_write_tokens: cacheWriteTokens,
654
707
  cached_tokens: cachedTokens,
655
708
  completion_tokens: completionTokens,
656
- prompt_tokens: promptTokens
709
+ cost,
710
+ prompt_tokens: promptTokens,
711
+ reasoning_tokens: reasoningTokens,
712
+ upstream_inference_cost: upstreamInferenceCost
657
713
  });
714
+ if (isRecord(usage.server_tool_use)) {
715
+ normalized.serverToolUse = Object.fromEntries(Object.entries(usage.server_tool_use).filter((entry) => typeof entry[1] === "number"));
716
+ }
717
+ return normalized;
658
718
  };
659
- var processSSELine = function* (line, pendingToolCalls, currentUsage) {
719
+ var processSSELine = function* (line, pendingToolCalls) {
660
720
  const trimmed = line.trim();
661
721
  if (!trimmed || !trimmed.startsWith("data: ")) {
662
722
  return;
@@ -664,7 +724,6 @@ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
664
724
  const data = trimmed.slice(SSE_DATA_PREFIX_LENGTH);
665
725
  if (data === DONE_SENTINEL) {
666
726
  yield* flushPendingToolCalls(pendingToolCalls);
667
- yield { type: "done", usage: currentUsage };
668
727
  return;
669
728
  }
670
729
  let parsed;
@@ -677,6 +736,10 @@ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
677
736
  if (usageUpdate) {
678
737
  yield { type: "usage_update", usage: usageUpdate };
679
738
  }
739
+ const metadata = narrowResponseMetadata(parsed);
740
+ if (metadata) {
741
+ yield { metadata, type: "response_metadata" };
742
+ }
680
743
  const { choices } = parsed;
681
744
  if (!isRecordArray(choices)) {
682
745
  return;
@@ -688,18 +751,30 @@ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
688
751
  yield* processChoice(firstChoice, pendingToolCalls);
689
752
  };
690
753
  var isUsageUpdate = (chunk) => chunk.type === "usage_update";
691
- var collectYieldableChunks = (line, pendingToolCalls, usageRef) => {
692
- const allChunks = Array.from(processSSELine(line, pendingToolCalls, usageRef.current));
754
+ var collectYieldableChunks = (line, pendingToolCalls, usageRef, metadataRef) => {
755
+ const allChunks = Array.from(processSSELine(line, pendingToolCalls));
693
756
  const usageChunks = allChunks.filter(isUsageUpdate);
694
757
  const lastUsage = usageChunks.at(NOT_FOUND);
695
758
  if (lastUsage) {
696
759
  usageRef.current = lastUsage.usage;
697
760
  }
698
- return allChunks.filter((chunk) => !isUsageUpdate(chunk));
761
+ const metadataChunks = allChunks.filter((chunk) => chunk.type === "response_metadata");
762
+ const lastMetadata = metadataChunks.at(NOT_FOUND);
763
+ if (lastMetadata && "metadata" in lastMetadata) {
764
+ metadataRef.current = {
765
+ ...metadataRef.current,
766
+ ...lastMetadata.metadata,
767
+ providerMetadata: {
768
+ ...metadataRef.current?.providerMetadata,
769
+ ...lastMetadata.metadata.providerMetadata
770
+ }
771
+ };
772
+ }
773
+ return allChunks.filter((chunk) => !isUsageUpdate(chunk) && chunk.type !== "response_metadata");
699
774
  };
700
- var processSSELines = function* (lines, pendingToolCalls, usageRef) {
775
+ var processSSELines = function* (lines, pendingToolCalls, usageRef, metadataRef) {
701
776
  for (const line of lines) {
702
- yield* collectYieldableChunks(line, pendingToolCalls, usageRef);
777
+ yield* collectYieldableChunks(line, pendingToolCalls, usageRef, metadataRef);
703
778
  }
704
779
  };
705
780
  var processStreamValue = (value, decoder, state) => {
@@ -712,51 +787,66 @@ var processStreamValue = (value, decoder, state) => {
712
787
  var drainReader = async function* (reader, decoder, state, signal) {
713
788
  for (let result = await reader.read();!result.done && !signal?.aborted; result = await reader.read()) {
714
789
  const lines = processStreamValue(result.value, decoder, state);
715
- yield* processSSELines(lines, state.pendingToolCalls, state.usageRef);
790
+ yield* processSSELines(lines, state.pendingToolCalls, state.usageRef, state.metadataRef);
716
791
  }
717
792
  };
718
- var parseSSEStream = async function* (body, signal) {
793
+ var parseSSEStream = async function* (body, initialMetadata, signal) {
719
794
  const reader = body.getReader();
720
795
  const decoder = new TextDecoder;
721
796
  const state = {
722
797
  buffer: "",
798
+ metadataRef: { current: initialMetadata },
723
799
  pendingToolCalls: new Map,
724
800
  usageRef: { current: undefined }
725
801
  };
726
802
  try {
727
803
  yield* drainReader(reader, decoder, state, signal);
728
- yield { type: "done", usage: state.usageRef.current };
804
+ yield {
805
+ metadata: state.metadataRef.current,
806
+ type: "done",
807
+ usage: state.usageRef.current
808
+ };
729
809
  } finally {
730
810
  reader.releaseLock();
731
811
  }
732
812
  };
733
- var fetchOpenAIStream = async function* (baseUrl, apiKey, body, signal) {
813
+ var fetchOpenAIStream = async function* (baseUrl, apiKey, body, fetchImpl, headers, providerName, signal) {
734
814
  const target = `${baseUrl}/v1/chat/completions`;
735
- const response = await fetch(target, {
815
+ const requestHeaders = new Headers(headers);
816
+ requestHeaders.set("Authorization", `Bearer ${apiKey}`);
817
+ requestHeaders.set("Content-Type", "application/json");
818
+ const response = await fetchImpl(target, {
736
819
  ...h2IfHttps(target),
737
820
  body: JSON.stringify(body),
738
- headers: {
739
- Authorization: `Bearer ${apiKey}`,
740
- "Content-Type": "application/json"
741
- },
821
+ headers: requestHeaders,
742
822
  method: "POST",
743
823
  signal
744
824
  });
745
825
  if (!response.ok) {
746
826
  const errorText = await response.text();
747
- throw ProviderError.fromResponse("openai", response.status, errorText);
827
+ throw ProviderError.fromResponse(providerName, response.status, errorText);
748
828
  }
749
829
  if (!response.body) {
750
830
  throw new ProviderError({
751
- message: "OpenAI API returned no response body",
752
- provider: "openai",
831
+ message: `${providerName} API returned no response body`,
832
+ provider: providerName,
753
833
  retryable: true
754
834
  });
755
835
  }
756
- yield* parseSSEStream(response.body, signal);
836
+ yield* parseSSEStream(response.body, {
837
+ generationId: response.headers.get("X-Generation-Id") ?? undefined,
838
+ providerMetadata: {
839
+ cacheAge: response.headers.get("X-OpenRouter-Cache-Age") ?? undefined,
840
+ cacheSourceId: response.headers.get("X-OpenRouter-Cache-Source-Id") ?? undefined,
841
+ cacheStatus: response.headers.get("X-OpenRouter-Cache-Status") ?? undefined,
842
+ cacheTtl: response.headers.get("X-OpenRouter-Cache-TTL") ?? undefined
843
+ }
844
+ }, signal);
757
845
  };
758
846
  var openai = (config2) => {
759
847
  const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL;
848
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
849
+ const providerName = config2.providerName ?? "openai";
760
850
  if (!config2.apiKey && !config2.tokenSource) {
761
851
  throw new Error("openai() requires either apiKey or tokenSource");
762
852
  }
@@ -766,15 +856,20 @@ var openai = (config2) => {
766
856
  }
767
857
  return config2.apiKey;
768
858
  };
859
+ const resolveHeaders = async (params) => typeof config2.headers === "function" ? await config2.headers(params) : config2.headers ?? {};
769
860
  return instrumentAIProvider({
770
861
  stream: (params) => {
771
- const body = buildRequestBody(params);
862
+ const openaiBody = buildRequestBody(params, config2.modelForCapabilities?.(params.model) ?? params.model);
863
+ const body = config2.transformRequestBody ? config2.transformRequestBody(openaiBody, params) : openaiBody;
772
864
  return async function* () {
773
- const apiKey = await resolveKey();
774
- yield* fetchOpenAIStream(baseUrl, apiKey, body, params.signal);
865
+ const [apiKey, headers] = await Promise.all([
866
+ resolveKey(),
867
+ resolveHeaders(params)
868
+ ]);
869
+ yield* fetchOpenAIStream(baseUrl, apiKey, body, fetchImpl, headers, providerName, params.signal);
775
870
  }();
776
871
  }
777
- }, "openai");
872
+ }, providerName);
778
873
  };
779
874
 
780
875
  // src/ai/providers/openaiCompatible.ts
@@ -826,7 +921,7 @@ var mapBlockToResponsesFormat = (block) => {
826
921
  if (block.type === "image") {
827
922
  return {
828
923
  image_url: {
829
- url: `data:${block.source.media_type};base64,${block.source.data}`
924
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
830
925
  },
831
926
  type: "input_image"
832
927
  };
@@ -834,12 +929,24 @@ var mapBlockToResponsesFormat = (block) => {
834
929
  if (block.type === "document") {
835
930
  return {
836
931
  file: {
837
- file_data: `data:${block.source.media_type};base64,${block.source.data}`,
932
+ file_data: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`,
838
933
  filename: block.name ?? "document.pdf"
839
934
  },
840
935
  type: "input_file"
841
936
  };
842
937
  }
938
+ if (block.type === "audio") {
939
+ return {
940
+ input_audio: { data: block.source.data, format: block.source.format },
941
+ type: "input_audio"
942
+ };
943
+ }
944
+ if (block.type === "video") {
945
+ return {
946
+ type: "input_video",
947
+ video_url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
948
+ };
949
+ }
843
950
  return null;
844
951
  };
845
952
  var mapContentToResponsesFormat = (content) => {
@@ -903,7 +1010,7 @@ var buildTools = (tools, isImageModel) => {
903
1010
  }
904
1011
  return result.length > 0 ? result : undefined;
905
1012
  };
906
- var buildRequestBody2 = (params, isImageModel) => {
1013
+ var buildRequestBody2 = (params, isImageModel, capabilityModel = params.model) => {
907
1014
  const body = {
908
1015
  input: buildInput(params.messages),
909
1016
  model: params.model,
@@ -955,8 +1062,8 @@ var buildRequestBody2 = (params, isImageModel) => {
955
1062
  };
956
1063
  }
957
1064
  }
958
- if (params.reasoning && isOpenAIReasoningModel(params.model)) {
959
- const effort = openaiEffortValue(params.model, params.reasoning);
1065
+ if (params.reasoning && isOpenAIReasoningModel(capabilityModel)) {
1066
+ const effort = openaiEffortValue(capabilityModel, params.reasoning);
960
1067
  if (effort) {
961
1068
  body.reasoning = {
962
1069
  effort,
@@ -1193,24 +1300,28 @@ var parseSSEStream2 = async function* (body, signal) {
1193
1300
  reader.releaseLock();
1194
1301
  }
1195
1302
  };
1196
- var fetchResponsesStream = async function* (baseUrl, apiKey, body, signal) {
1303
+ var fetchResponsesStream = async function* (baseUrl, apiKey, body, fetchImpl, headers, providerName, signal) {
1197
1304
  const target = `${baseUrl}/v1/responses`;
1198
- const response = await fetch(target, {
1305
+ const requestHeaders = new Headers(headers);
1306
+ requestHeaders.set("Authorization", `Bearer ${apiKey}`);
1307
+ requestHeaders.set("Content-Type", "application/json");
1308
+ const response = await fetchImpl(target, {
1199
1309
  ...h2IfHttps2(target),
1200
1310
  body: JSON.stringify(body),
1201
- headers: {
1202
- Authorization: `Bearer ${apiKey}`,
1203
- "Content-Type": "application/json"
1204
- },
1311
+ headers: requestHeaders,
1205
1312
  method: "POST",
1206
1313
  signal
1207
1314
  });
1208
1315
  if (!response.ok) {
1209
1316
  const errorText = await response.text();
1210
- throw new Error(`OpenAI Responses API error ${response.status}: ${errorText}`);
1317
+ throw ProviderError.fromResponse(providerName, response.status, errorText);
1211
1318
  }
1212
1319
  if (!response.body) {
1213
- throw new Error("OpenAI Responses API returned no response body");
1320
+ throw new ProviderError({
1321
+ message: `${providerName} Responses API returned no response body`,
1322
+ provider: providerName,
1323
+ retryable: true
1324
+ });
1214
1325
  }
1215
1326
  yield* parseSSEStream2(response.body, signal);
1216
1327
  };
@@ -1224,15 +1335,28 @@ var resolveImageModels = (imageModels) => {
1224
1335
  return new Set(imageModels);
1225
1336
  };
1226
1337
  var openaiResponses = (config2) => {
1338
+ if (!config2.apiKey && !config2.tokenSource)
1339
+ throw new Error("openaiResponses() requires either apiKey or tokenSource");
1227
1340
  const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL2;
1341
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
1228
1342
  const imageModels = resolveImageModels(config2.imageModels);
1343
+ const providerName = config2.providerName ?? "openai-responses";
1344
+ const resolveKey = async () => config2.tokenSource ? await Promise.resolve(config2.tokenSource()) : config2.apiKey;
1345
+ const resolveHeaders = async (params) => typeof config2.headers === "function" ? await config2.headers(params) : config2.headers ?? {};
1229
1346
  return instrumentAIProvider({
1230
1347
  stream: (params) => {
1231
1348
  const isImageModel = imageModels.has(params.model);
1232
- const body = buildRequestBody2(params, isImageModel);
1233
- return fetchResponsesStream(baseUrl, config2.apiKey, body, params.signal);
1349
+ const builtBody = buildRequestBody2(params, isImageModel, config2.modelForCapabilities?.(params.model) ?? params.model);
1350
+ const body = config2.transformRequestBody ? config2.transformRequestBody(builtBody, params) : builtBody;
1351
+ return async function* () {
1352
+ const [apiKey, headers] = await Promise.all([
1353
+ resolveKey(),
1354
+ resolveHeaders(params)
1355
+ ]);
1356
+ yield* fetchResponsesStream(baseUrl, apiKey, body, fetchImpl, headers, providerName, params.signal);
1357
+ }();
1234
1358
  }
1235
- }, "openai-responses");
1359
+ }, providerName);
1236
1360
  };
1237
1361
 
1238
1362
  // src/ai/providers/gemini.ts
@@ -1251,14 +1375,43 @@ var mapContentBlock = (block) => {
1251
1375
  case "text":
1252
1376
  return { text: block.content };
1253
1377
  case "image":
1254
- return {
1378
+ return block.source.type === "url" ? {
1379
+ fileData: {
1380
+ fileUri: block.source.url,
1381
+ mimeType: block.source.media_type
1382
+ }
1383
+ } : {
1255
1384
  inlineData: {
1256
1385
  data: block.source.data,
1257
1386
  mimeType: block.source.media_type
1258
1387
  }
1259
1388
  };
1260
1389
  case "document":
1390
+ return block.source.type === "url" ? {
1391
+ fileData: {
1392
+ fileUri: block.source.url,
1393
+ mimeType: block.source.media_type
1394
+ }
1395
+ } : {
1396
+ inlineData: {
1397
+ data: block.source.data,
1398
+ mimeType: block.source.media_type
1399
+ }
1400
+ };
1401
+ case "audio":
1261
1402
  return {
1403
+ inlineData: {
1404
+ data: block.source.data,
1405
+ mimeType: `audio/${block.source.format}`
1406
+ }
1407
+ };
1408
+ case "video":
1409
+ return block.source.type === "url" ? {
1410
+ fileData: {
1411
+ fileUri: block.source.url,
1412
+ mimeType: block.source.media_type
1413
+ }
1414
+ } : {
1262
1415
  inlineData: {
1263
1416
  data: block.source.data,
1264
1417
  mimeType: block.source.media_type
@@ -1571,6 +1724,9 @@ var mapContentBlock2 = (block) => {
1571
1724
  type: "tool_use"
1572
1725
  };
1573
1726
  }
1727
+ if (block.type === "audio" || block.type === "video") {
1728
+ throw new Error(`Anthropic does not support ${block.type} content blocks`);
1729
+ }
1574
1730
  return { text: block.content, type: "text" };
1575
1731
  };
1576
1732
  var mapMessage = (msg) => ({
@@ -2228,6 +2384,429 @@ var ollama = (config2 = {}) => {
2228
2384
  }, "ollama");
2229
2385
  };
2230
2386
 
2387
+ // src/ai/providers/openrouterClient.ts
2388
+ var DEFAULT_BASE_URL6 = "https://openrouter.ai/api/v1";
2389
+ var withoutLatestPrefix = (model) => model.startsWith("~") ? model.slice(1) : model;
2390
+ var openRouterModelMatchesRule = (model, rule) => {
2391
+ const normalizedModel = withoutLatestPrefix(model);
2392
+ const normalizedRule = withoutLatestPrefix(rule);
2393
+ return normalizedRule.endsWith("/*") ? normalizedModel.startsWith(normalizedRule.slice(0, -1)) : normalizedModel === normalizedRule;
2394
+ };
2395
+ var assertAllowedModel = (model, allowedModels) => {
2396
+ if (!allowedModels)
2397
+ return;
2398
+ if (allowedModels.some((rule) => openRouterModelMatchesRule(model, rule)))
2399
+ return;
2400
+ throw new Error(`OpenRouter model "${model}" is not allowed`);
2401
+ };
2402
+ var normalizePath = (path) => path.startsWith("/") ? path : `/${path}`;
2403
+ var encodeModelPath = (model) => model.split("/").map(encodeURIComponent).join("/");
2404
+ var withQuery = (url, query) => {
2405
+ if (!query)
2406
+ return url;
2407
+ const result = new URL(url);
2408
+ for (const [key, value] of Object.entries(query)) {
2409
+ if (value !== undefined)
2410
+ result.searchParams.set(key, String(value));
2411
+ }
2412
+ return result.toString();
2413
+ };
2414
+ var createOpenRouterClient = (config2) => {
2415
+ if (!config2.apiKey && !config2.tokenSource)
2416
+ throw new Error("createOpenRouterClient() requires either apiKey or tokenSource");
2417
+ const baseUrl = (config2.baseUrl ?? DEFAULT_BASE_URL6).replace(/\/$/, "");
2418
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
2419
+ const allowedModels = config2.allowedModels ? [...config2.allowedModels] : undefined;
2420
+ const requestRaw = async (path, options = {}) => {
2421
+ const token = config2.tokenSource ? await Promise.resolve(config2.tokenSource()) : config2.apiKey;
2422
+ const suppliedHeaders = typeof config2.headers === "function" ? await config2.headers() : config2.headers ?? {};
2423
+ const headers = new Headers(suppliedHeaders);
2424
+ new Headers(options.headers).forEach((value, key) => headers.set(key, value));
2425
+ headers.set("Authorization", `Bearer ${token}`);
2426
+ let body;
2427
+ if (options.body instanceof FormData || options.body instanceof Blob) {
2428
+ body = options.body;
2429
+ } else if (options.body !== undefined) {
2430
+ headers.set("Content-Type", "application/json");
2431
+ body = JSON.stringify(options.body);
2432
+ }
2433
+ const { query, ...requestInit } = options;
2434
+ const response = await fetchImpl(withQuery(`${baseUrl}${normalizePath(path)}`, options.query), { ...requestInit, body, headers });
2435
+ if (!response.ok) {
2436
+ throw ProviderError.fromResponse("openrouter", response.status, await response.text());
2437
+ }
2438
+ return response;
2439
+ };
2440
+ const request = async (path, options = {}) => (await requestRaw(path, options)).json();
2441
+ const listModels = async (query) => {
2442
+ const result = await request("/models", { query });
2443
+ if (!allowedModels)
2444
+ return result;
2445
+ return {
2446
+ ...result,
2447
+ data: result.data.filter((model) => allowedModels.some((rule) => openRouterModelMatchesRule(model.id, rule)))
2448
+ };
2449
+ };
2450
+ const filterModelList = (result) => {
2451
+ if (!allowedModels)
2452
+ return result;
2453
+ return {
2454
+ ...result,
2455
+ data: result.data.filter((model) => allowedModels.some((rule) => openRouterModelMatchesRule(model.id, rule)))
2456
+ };
2457
+ };
2458
+ return {
2459
+ cancelBatch: (id) => request(`/batches/${encodeURIComponent(id)}/cancel`, {
2460
+ method: "POST"
2461
+ }),
2462
+ createBatch: (body) => request("/batches", { body, method: "POST" }),
2463
+ createEmbedding: (body) => {
2464
+ assertAllowedModel(body.model, allowedModels);
2465
+ return request("/embeddings", {
2466
+ body,
2467
+ method: "POST"
2468
+ });
2469
+ },
2470
+ generateImage: (body) => {
2471
+ assertAllowedModel(body.model, allowedModels);
2472
+ return request("/images", {
2473
+ body,
2474
+ method: "POST"
2475
+ });
2476
+ },
2477
+ generateVideo: (body) => {
2478
+ assertAllowedModel(body.model, allowedModels);
2479
+ return request("/videos", {
2480
+ body,
2481
+ method: "POST"
2482
+ });
2483
+ },
2484
+ getBatch: (id) => request(`/batches/${encodeURIComponent(id)}`),
2485
+ getCredits: () => request("/credits"),
2486
+ getCurrentKey: () => request("/key"),
2487
+ getGeneration: (id) => request("/generation", {
2488
+ query: { id }
2489
+ }),
2490
+ getModelEndpoints: (model) => {
2491
+ assertAllowedModel(model, allowedModels);
2492
+ return request(`/models/${encodeModelPath(model)}/endpoints`);
2493
+ },
2494
+ getVideo: (id) => request(`/videos/${encodeURIComponent(id)}`),
2495
+ listImageModels: async () => filterModelList(await request("/images/models")),
2496
+ listModels,
2497
+ listPresets: (offset = 0, limit = 100) => request("/presets", { query: { limit, offset } }),
2498
+ listProviders: () => request("/providers"),
2499
+ listRerankModels: async () => filterModelList(await request("/rerank/models")),
2500
+ listVideoModels: async () => filterModelList(await request("/videos/models")),
2501
+ request,
2502
+ requestRaw,
2503
+ respond: (body) => {
2504
+ assertAllowedModel(body.model, allowedModels);
2505
+ return body.stream ? requestRaw("/responses", { body, method: "POST" }) : request("/responses", {
2506
+ body,
2507
+ method: "POST"
2508
+ });
2509
+ },
2510
+ rerank: (body) => {
2511
+ assertAllowedModel(body.model, allowedModels);
2512
+ return request("/rerank", {
2513
+ body,
2514
+ method: "POST"
2515
+ });
2516
+ },
2517
+ speak: (body) => {
2518
+ assertAllowedModel(body.model, allowedModels);
2519
+ return requestRaw("/audio/speech", { body, method: "POST" });
2520
+ },
2521
+ transcribe: (body) => {
2522
+ if (body instanceof FormData) {
2523
+ const model = body.get("model");
2524
+ if (typeof model !== "string")
2525
+ throw new Error("OpenRouter transcription FormData requires model");
2526
+ assertAllowedModel(model, allowedModels);
2527
+ } else {
2528
+ assertAllowedModel(body.model, allowedModels);
2529
+ }
2530
+ return request("/audio/transcriptions", {
2531
+ body,
2532
+ method: "POST"
2533
+ });
2534
+ }
2535
+ };
2536
+ };
2537
+
2538
+ // src/ai/providers/openrouter.ts
2539
+ var DEFAULT_BASE_URL7 = "https://openrouter.ai/api";
2540
+ var MAX_APP_CATEGORIES = 2;
2541
+ var withoutLatestPrefix2 = (model) => model.startsWith("~") ? model.slice(1) : model;
2542
+ var modelForOpenAICapabilities = (model) => {
2543
+ const normalized = withoutLatestPrefix2(model);
2544
+ return normalized.startsWith("openai/") ? normalized.slice("openai/".length) : normalized;
2545
+ };
2546
+ var modelMatchesRule = (model, rule) => {
2547
+ const normalizedModel = withoutLatestPrefix2(model);
2548
+ const normalizedRule = withoutLatestPrefix2(rule);
2549
+ if (normalizedRule.endsWith("/*")) {
2550
+ return normalizedModel.startsWith(normalizedRule.slice(0, -1));
2551
+ }
2552
+ return normalizedModel === normalizedRule;
2553
+ };
2554
+ var providerMatchesRule = (provider, rule) => provider === rule || provider.startsWith(`${rule}/`);
2555
+ var assertNonEmptyPolicy = (label, value) => {
2556
+ if (value && value.length === 0) {
2557
+ throw new Error(`openrouter() ${label} must not be empty`);
2558
+ }
2559
+ };
2560
+ var assertRoutingPolicy = (config2) => {
2561
+ assertNonEmptyPolicy("allowedProviders", config2.allowedProviders);
2562
+ assertNonEmptyPolicy("routing.only", config2.routing?.only);
2563
+ assertNonEmptyPolicy("allowedPresets", config2.allowedPresets);
2564
+ if (config2.appCategories && config2.appCategories.length > MAX_APP_CATEGORIES) {
2565
+ throw new Error("openrouter() appCategories supports at most 2 entries");
2566
+ }
2567
+ if (!config2.allowedProviders)
2568
+ return;
2569
+ const selected = [
2570
+ ...config2.routing?.only ?? [],
2571
+ ...config2.routing?.order ?? []
2572
+ ];
2573
+ const denied = selected.find((provider) => !config2.allowedProviders.some((rule) => providerMatchesRule(provider, rule)));
2574
+ if (denied) {
2575
+ throw new Error(`openrouter() provider "${denied}" is outside allowedProviders`);
2576
+ }
2577
+ };
2578
+ var assertRequestRoutingPolicy = (routing, allowedProviders) => {
2579
+ assertRoutingPolicy({
2580
+ allowedProviders,
2581
+ apiKey: "policy-validation",
2582
+ routing
2583
+ });
2584
+ };
2585
+ var mapRouting = (routing, allowedProviders) => {
2586
+ const only = routing?.only ?? allowedProviders;
2587
+ const wire = {};
2588
+ if (typeof routing?.allowFallbacks === "boolean")
2589
+ wire.allow_fallbacks = routing.allowFallbacks;
2590
+ if (routing?.dataCollection)
2591
+ wire.data_collection = routing.dataCollection;
2592
+ if (typeof routing?.enforceDistillableText === "boolean")
2593
+ wire.enforce_distillable_text = routing.enforceDistillableText;
2594
+ if (routing?.ignore)
2595
+ wire.ignore = [...routing.ignore];
2596
+ if (routing?.maxPrice)
2597
+ wire.max_price = { ...routing.maxPrice };
2598
+ if (only)
2599
+ wire.only = [...only];
2600
+ if (routing?.order)
2601
+ wire.order = [...routing.order];
2602
+ if (routing?.preferredMaxLatency !== undefined)
2603
+ wire.preferred_max_latency = routing.preferredMaxLatency;
2604
+ if (routing?.preferredMinThroughput !== undefined)
2605
+ wire.preferred_min_throughput = routing.preferredMinThroughput;
2606
+ if (routing?.quantizations)
2607
+ wire.quantizations = [...routing.quantizations];
2608
+ if (typeof routing?.requireParameters === "boolean")
2609
+ wire.require_parameters = routing.requireParameters;
2610
+ if (routing?.sort)
2611
+ wire.sort = routing.sort;
2612
+ if (typeof routing?.zdr === "boolean")
2613
+ wire.zdr = routing.zdr;
2614
+ return wire;
2615
+ };
2616
+ var requestOptionsFor = (params, defaults) => {
2617
+ const supplied = params.providerOptions?.openrouter;
2618
+ if (supplied !== undefined && (typeof supplied !== "object" || !supplied)) {
2619
+ throw new Error("providerOptions.openrouter must be an object");
2620
+ }
2621
+ return {
2622
+ ...defaults,
2623
+ ...supplied
2624
+ };
2625
+ };
2626
+ var resolveAttributionHeaders = async (config2, params) => {
2627
+ const supplied = typeof config2.headers === "function" ? await config2.headers() : config2.headers ?? {};
2628
+ const headers = new Headers(supplied);
2629
+ if (config2.appUrl)
2630
+ headers.set("HTTP-Referer", config2.appUrl);
2631
+ if (config2.appName)
2632
+ headers.set("X-OpenRouter-Title", config2.appName);
2633
+ if (config2.appCategories?.length) {
2634
+ headers.set("X-OpenRouter-Categories", config2.appCategories.join(","));
2635
+ }
2636
+ const options = requestOptionsFor(params, config2.requestOptions);
2637
+ if (options.routerMetadata ?? true)
2638
+ headers.set("X-OpenRouter-Metadata", "enabled");
2639
+ if (options.sessionId)
2640
+ headers.set("X-Session-Id", options.sessionId);
2641
+ if (options.responseCache) {
2642
+ headers.set("X-OpenRouter-Cache", options.responseCache.enabled ? "true" : "false");
2643
+ if (options.responseCache.ttlSeconds !== undefined)
2644
+ headers.set("X-OpenRouter-Cache-TTL", String(options.responseCache.ttlSeconds));
2645
+ if (options.responseCache.clear)
2646
+ headers.set("X-OpenRouter-Cache-Clear", "true");
2647
+ }
2648
+ return headers;
2649
+ };
2650
+ var SECURITY_SENSITIVE_EXTRA_BODY_FIELDS = new Set([
2651
+ "messages",
2652
+ "model",
2653
+ "models",
2654
+ "plugins",
2655
+ "preset",
2656
+ "provider",
2657
+ "stream",
2658
+ "tools"
2659
+ ]);
2660
+ var assertAllowedPreset = (preset, allowedPresets) => {
2661
+ if (!preset)
2662
+ return;
2663
+ if (allowedPresets?.includes(preset))
2664
+ return;
2665
+ throw new Error(`OpenRouter preset "${preset}" is not allowed`);
2666
+ };
2667
+ var assertIndirectModels = (value, allowedModels, key = "") => {
2668
+ if (key === "model" && typeof value === "string")
2669
+ assertAllowedModel2(value, allowedModels);
2670
+ if (key === "models" && Array.isArray(value)) {
2671
+ for (const model of value) {
2672
+ if (typeof model === "string")
2673
+ assertAllowedModel2(model, allowedModels);
2674
+ }
2675
+ }
2676
+ if (Array.isArray(value)) {
2677
+ for (const item of value)
2678
+ assertIndirectModels(item, allowedModels);
2679
+ } else if (value && typeof value === "object") {
2680
+ for (const [childKey, child] of Object.entries(value))
2681
+ assertIndirectModels(child, allowedModels, childKey);
2682
+ }
2683
+ };
2684
+ var assertRequestOptions = (options, allowedModels, allowedPresets, allowedProviders) => {
2685
+ assertAllowedPreset(options.preset, allowedPresets);
2686
+ assertRequestRoutingPolicy(options.routing, allowedProviders);
2687
+ if (options.sessionId && options.sessionId.length > 256)
2688
+ throw new Error("OpenRouter sessionId must be at most 256 characters");
2689
+ const ttl = options.responseCache?.ttlSeconds;
2690
+ if (ttl !== undefined && (!Number.isInteger(ttl) || ttl < 1 || ttl > 86400))
2691
+ throw new Error("OpenRouter response-cache TTL must be 1-86400 seconds");
2692
+ if (options.responseCache?.clear && !options.responseCache.enabled)
2693
+ throw new Error("OpenRouter cache clear requires response caching enabled");
2694
+ if (options.fallbackModels) {
2695
+ if (options.fallbackModels.length === 0)
2696
+ throw new Error("OpenRouter fallbackModels must not be empty");
2697
+ for (const model of options.fallbackModels)
2698
+ assertAllowedModel2(model, allowedModels);
2699
+ }
2700
+ assertIndirectModels(options.serverTools, allowedModels);
2701
+ assertIndirectModels(options.plugins, allowedModels);
2702
+ if (options.extraBody) {
2703
+ const unsafe = Object.keys(options.extraBody).find((key) => SECURITY_SENSITIVE_EXTRA_BODY_FIELDS.has(key));
2704
+ if (unsafe)
2705
+ throw new Error(`OpenRouter extraBody cannot override "${unsafe}"`);
2706
+ }
2707
+ };
2708
+ var assertAllowedModel2 = (model, allowedModels) => {
2709
+ if (!allowedModels)
2710
+ return;
2711
+ if (allowedModels.some((rule) => modelMatchesRule(model, rule)))
2712
+ return;
2713
+ throw new Error(`OpenRouter model "${model}" is not allowed`);
2714
+ };
2715
+ var snapshotPolicy = (config2) => ({
2716
+ allowedModels: config2.allowedModels ? [...config2.allowedModels] : undefined,
2717
+ allowedPresets: config2.allowedPresets ? [...config2.allowedPresets] : undefined
2718
+ });
2719
+ var transformOpenRouterRequest = (config2, allowedModels, allowedPresets, body, params) => {
2720
+ const options = requestOptionsFor(params, config2.requestOptions);
2721
+ assertRequestOptions(options, allowedModels, allowedPresets, config2.allowedProviders);
2722
+ const transformed = { ...body, ...options.extraBody };
2723
+ if (params.reasoning?.budgetTokens !== undefined) {
2724
+ transformed.reasoning = { max_tokens: params.reasoning.budgetTokens };
2725
+ delete transformed.reasoning_effort;
2726
+ } else if (params.reasoning?.effort) {
2727
+ transformed.reasoning = { effort: params.reasoning.effort };
2728
+ delete transformed.reasoning_effort;
2729
+ }
2730
+ const routing = mapRouting({ ...config2.routing, ...options.routing }, config2.allowedProviders);
2731
+ if (Object.keys(routing).length > 0)
2732
+ transformed.provider = routing;
2733
+ if (options.fallbackModels)
2734
+ transformed.models = [...options.fallbackModels];
2735
+ if (options.includeReasoning !== undefined)
2736
+ transformed.include_reasoning = options.includeReasoning;
2737
+ if (options.maxToolCalls !== undefined)
2738
+ transformed.max_tool_calls = options.maxToolCalls;
2739
+ if (options.plugins)
2740
+ transformed.plugins = [...options.plugins];
2741
+ if (options.preset)
2742
+ transformed.preset = options.preset;
2743
+ if (options.serverTools) {
2744
+ transformed.tools = [
2745
+ ...Array.isArray(transformed.tools) ? transformed.tools : [],
2746
+ ...options.serverTools
2747
+ ];
2748
+ }
2749
+ if (options.serviceTier)
2750
+ transformed.service_tier = options.serviceTier;
2751
+ if (options.sessionId)
2752
+ transformed.session_id = options.sessionId;
2753
+ if (options.stopServerToolsWhen)
2754
+ transformed.stop_server_tools_when = options.stopServerToolsWhen;
2755
+ if (options.transforms)
2756
+ transformed.transforms = [...options.transforms];
2757
+ if (options.user)
2758
+ transformed.user = options.user;
2759
+ if (options.verbosity)
2760
+ transformed.verbosity = options.verbosity;
2761
+ return transformed;
2762
+ };
2763
+ var withOpenRouterPolicy = (provider, allowedModels, allowedPresets) => ({
2764
+ stream: (params) => {
2765
+ if (params.model.startsWith("@preset/")) {
2766
+ assertAllowedPreset(params.model.slice("@preset/".length), allowedPresets);
2767
+ } else {
2768
+ const presetSeparator = params.model.indexOf("@preset/");
2769
+ if (presetSeparator >= 0) {
2770
+ assertAllowedModel2(params.model.slice(0, presetSeparator), allowedModels);
2771
+ assertAllowedPreset(params.model.slice(presetSeparator + "@preset/".length), allowedPresets);
2772
+ } else {
2773
+ assertAllowedModel2(params.model, allowedModels);
2774
+ }
2775
+ }
2776
+ return provider.stream(params);
2777
+ }
2778
+ });
2779
+ var openrouter = (config2) => {
2780
+ assertRoutingPolicy(config2);
2781
+ const { allowedModels, allowedPresets } = snapshotPolicy(config2);
2782
+ const provider = openai({
2783
+ apiKey: config2.apiKey,
2784
+ baseUrl: config2.baseUrl ?? DEFAULT_BASE_URL7,
2785
+ fetch: config2.fetch,
2786
+ headers: (params) => resolveAttributionHeaders(config2, params),
2787
+ modelForCapabilities: modelForOpenAICapabilities,
2788
+ providerName: "openrouter",
2789
+ tokenSource: config2.tokenSource,
2790
+ transformRequestBody: (body, params) => transformOpenRouterRequest(config2, allowedModels, allowedPresets, body, params)
2791
+ });
2792
+ return withOpenRouterPolicy(provider, allowedModels, allowedPresets);
2793
+ };
2794
+ var openrouterResponses = (config2) => {
2795
+ assertRoutingPolicy(config2);
2796
+ const { allowedModels, allowedPresets } = snapshotPolicy(config2);
2797
+ const provider = openaiResponses({
2798
+ apiKey: config2.apiKey,
2799
+ baseUrl: config2.baseUrl ?? DEFAULT_BASE_URL7,
2800
+ fetch: config2.fetch,
2801
+ headers: (params) => resolveAttributionHeaders(config2, params),
2802
+ modelForCapabilities: modelForOpenAICapabilities,
2803
+ providerName: "openrouter",
2804
+ tokenSource: config2.tokenSource,
2805
+ transformRequestBody: (body, params) => transformOpenRouterRequest(config2, allowedModels, allowedPresets, body, params)
2806
+ });
2807
+ return withOpenRouterPolicy(provider, allowedModels, allowedPresets);
2808
+ };
2809
+
2231
2810
  // src/plugins/aiChat.ts
2232
2811
  import { Elysia } from "elysia";
2233
2812
 
@@ -2644,6 +3223,7 @@ var processToolTurn = async (socket, options, state, messageId, conversationId,
2644
3223
  messages: state.currentMessages,
2645
3224
  model: options.model,
2646
3225
  promptCaching: options.promptCaching,
3226
+ providerOptions: options.providerOptions,
2647
3227
  reasoning: options.reasoning,
2648
3228
  signal,
2649
3229
  systemPrompt: options.systemPrompt,
@@ -2730,6 +3310,7 @@ var processStream = async (socket, options, messages, messageId, conversationId,
2730
3310
  messages,
2731
3311
  model: options.model,
2732
3312
  promptCaching: options.promptCaching,
3313
+ providerOptions: options.providerOptions,
2733
3314
  reasoning: options.reasoning,
2734
3315
  signal,
2735
3316
  systemPrompt: options.systemPrompt,
@@ -3111,6 +3692,7 @@ var streamTurns = async function* (options, renderers, messages, signal, startTi
3111
3692
  messages: turnState.currentMessages,
3112
3693
  model: options.model,
3113
3694
  promptCaching: options.promptCaching,
3695
+ providerOptions: options.providerOptions,
3114
3696
  reasoning: options.reasoning,
3115
3697
  signal,
3116
3698
  systemPrompt: options.systemPrompt,
@@ -3123,6 +3705,13 @@ var streamTurns = async function* (options, renderers, messages, signal, startTi
3123
3705
  aggregateUsage.outputTokens += chunkState.usage?.outputTokens ?? 0;
3124
3706
  aggregateUsage.cacheReadInputTokens = (aggregateUsage.cacheReadInputTokens ?? 0) + (chunkState.usage?.cacheReadInputTokens ?? 0);
3125
3707
  aggregateUsage.cacheWriteInputTokens = (aggregateUsage.cacheWriteInputTokens ?? 0) + (chunkState.usage?.cacheWriteInputTokens ?? 0);
3708
+ aggregateUsage.costCredits = (aggregateUsage.costCredits ?? 0) + (chunkState.usage?.costCredits ?? 0);
3709
+ aggregateUsage.reasoningTokens = (aggregateUsage.reasoningTokens ?? 0) + (chunkState.usage?.reasoningTokens ?? 0);
3710
+ for (const [key, value] of Object.entries(chunkState.usage?.serverToolUse ?? {})) {
3711
+ aggregateUsage.serverToolUse ??= {};
3712
+ aggregateUsage.serverToolUse[key] = (aggregateUsage.serverToolUse[key] ?? 0) + value;
3713
+ }
3714
+ aggregateUsage.upstreamInferenceCostCredits = (aggregateUsage.upstreamInferenceCostCredits ?? 0) + (chunkState.usage?.upstreamInferenceCostCredits ?? 0);
3126
3715
  const runningTotalTokens = aggregateUsage.inputTokens + aggregateUsage.outputTokens;
3127
3716
  if (chunkState.stopReason === "max_tokens") {
3128
3717
  finishReason = "max_tokens";
@@ -3606,6 +4195,7 @@ var generateAI = async (options) => {
3606
4195
  messages: options.messages,
3607
4196
  model: options.model,
3608
4197
  promptCaching: options.promptCaching,
4198
+ providerOptions: options.providerOptions,
3609
4199
  reasoning: options.reasoning,
3610
4200
  responseFormat: options.responseFormat,
3611
4201
  signal: options.signal,
@@ -3618,17 +4208,22 @@ var generateAI = async (options) => {
3618
4208
  });
3619
4209
  let text = "";
3620
4210
  const toolCalls = [];
4211
+ const citations = [];
3621
4212
  let usage;
4213
+ let metadata;
3622
4214
  for await (const chunk of stream) {
3623
4215
  if (chunk.type === "text") {
3624
4216
  text += chunk.content;
3625
4217
  } else if (chunk.type === "tool_use") {
3626
4218
  toolCalls.push({ id: chunk.id, input: chunk.input, name: chunk.name });
4219
+ } else if (chunk.type === "citation") {
4220
+ citations.push(chunk);
3627
4221
  } else if (chunk.type === "done") {
3628
4222
  usage = chunk.usage;
4223
+ metadata = chunk.metadata;
3629
4224
  }
3630
4225
  }
3631
- return { text, toolCalls, usage };
4226
+ return { citations, metadata, text, toolCalls, usage };
3632
4227
  };
3633
4228
  var DEFAULT_TOOL_MAX_TURNS = 6;
3634
4229
  var mergeUsage = (left, right) => {
@@ -3637,11 +4232,18 @@ var mergeUsage = (left, right) => {
3637
4232
  if (!right)
3638
4233
  return left;
3639
4234
  const add = (a, b) => a === undefined && b === undefined ? undefined : (a ?? 0) + (b ?? 0);
4235
+ const serverToolUse = { ...left.serverToolUse };
4236
+ for (const [key, value] of Object.entries(right.serverToolUse ?? {}))
4237
+ serverToolUse[key] = (serverToolUse[key] ?? 0) + value;
3640
4238
  return {
3641
4239
  cacheReadInputTokens: add(left.cacheReadInputTokens, right.cacheReadInputTokens),
3642
4240
  cacheWriteInputTokens: add(left.cacheWriteInputTokens, right.cacheWriteInputTokens),
4241
+ costCredits: add(left.costCredits, right.costCredits),
3643
4242
  inputTokens: (left.inputTokens ?? 0) + (right.inputTokens ?? 0),
3644
- outputTokens: (left.outputTokens ?? 0) + (right.outputTokens ?? 0)
4243
+ outputTokens: (left.outputTokens ?? 0) + (right.outputTokens ?? 0),
4244
+ reasoningTokens: add(left.reasoningTokens, right.reasoningTokens),
4245
+ serverToolUse: Object.keys(serverToolUse).length > 0 ? serverToolUse : undefined,
4246
+ upstreamInferenceCostCredits: add(left.upstreamInferenceCostCredits, right.upstreamInferenceCostCredits)
3645
4247
  };
3646
4248
  };
3647
4249
  var toProviderTools = (tools) => Object.entries(tools).map(([name, definition]) => ({
@@ -3825,6 +4427,7 @@ var streamAIWithTools = async function* (options) {
3825
4427
  messages,
3826
4428
  model: base.model,
3827
4429
  promptCaching: base.promptCaching,
4430
+ providerOptions: base.providerOptions,
3828
4431
  reasoning: base.reasoning,
3829
4432
  signal: base.signal,
3830
4433
  stopSequences: base.stopSequences,
@@ -6223,9 +6826,12 @@ export {
6223
6826
  parseChoiceSpec,
6224
6827
  parseChartSpec,
6225
6828
  parseAIMessage,
6829
+ openrouterResponses,
6830
+ openrouter,
6226
6831
  openaiResponses,
6227
6832
  openaiCompatible,
6228
6833
  openai,
6834
+ openRouterModelMatchesRule,
6229
6835
  ollama,
6230
6836
  moonshot,
6231
6837
  mistralai,
@@ -6245,6 +6851,7 @@ export {
6245
6851
  createUiCards,
6246
6852
  createSyncConversationStore,
6247
6853
  createProviderProxyResponse,
6854
+ createOpenRouterClient,
6248
6855
  createOAuth2ClientCredentialsTokenSource,
6249
6856
  createMemoryStore,
6250
6857
  createConversationManager,
@@ -6282,5 +6889,5 @@ export {
6282
6889
  BUILTIN_UI_CARDS
6283
6890
  };
6284
6891
 
6285
- //# debugId=8DDC9883DEF6AEEE64756E2164756E21
6892
+ //# debugId=A06FECA819C53C2464756E2164756E21
6286
6893
  //# sourceMappingURL=index.js.map