@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/README.md +117 -0
- package/dist/ai/index.js +661 -54
- package/dist/ai/index.js.map +14 -12
- package/dist/ai/providers/anthropic.js +6 -2
- package/dist/ai/providers/anthropic.js.map +4 -4
- package/dist/ai/providers/gemini.js +33 -3
- package/dist/ai/providers/gemini.js.map +4 -4
- package/dist/ai/providers/ollama.js +3 -2
- package/dist/ai/providers/ollama.js.map +3 -3
- package/dist/ai/providers/openai.js +130 -35
- package/dist/ai/providers/openai.js.map +4 -4
- package/dist/ai/providers/openaiCompatible.js +130 -35
- package/dist/ai/providers/openaiCompatible.js.map +4 -4
- package/dist/ai/providers/openaiResponses.js +48 -18
- package/dist/ai/providers/openaiResponses.js.map +4 -4
- package/dist/ai/providers/openrouter.js +1757 -0
- package/dist/ai/providers/openrouter.js.map +17 -0
- package/dist/src/ai/generateAI.d.ts +4 -1
- package/dist/src/ai/index.d.ts +2 -0
- package/dist/src/ai/providers/openai.d.ts +7 -3
- package/dist/src/ai/providers/openaiResponses.d.ts +9 -4
- package/dist/src/ai/providers/openrouter.d.ts +113 -0
- package/dist/src/ai/providers/openrouterClient.d.ts +147 -0
- package/dist/types/ai.d.ts +63 -6
- 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,
|
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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(
|
|
827
|
+
throw ProviderError.fromResponse(providerName, response.status, errorText);
|
|
748
828
|
}
|
|
749
829
|
if (!response.body) {
|
|
750
830
|
throw new ProviderError({
|
|
751
|
-
message:
|
|
752
|
-
provider:
|
|
831
|
+
message: `${providerName} API returned no response body`,
|
|
832
|
+
provider: providerName,
|
|
753
833
|
retryable: true
|
|
754
834
|
});
|
|
755
835
|
}
|
|
756
|
-
yield* parseSSEStream(response.body,
|
|
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
|
|
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
|
|
774
|
-
|
|
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
|
-
},
|
|
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(
|
|
959
|
-
const effort = openaiEffortValue(
|
|
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
|
|
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
|
|
1317
|
+
throw ProviderError.fromResponse(providerName, response.status, errorText);
|
|
1211
1318
|
}
|
|
1212
1319
|
if (!response.body) {
|
|
1213
|
-
throw new
|
|
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
|
|
1233
|
-
|
|
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
|
-
},
|
|
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=
|
|
6892
|
+
//# debugId=A06FECA819C53C2464756E2164756E21
|
|
6286
6893
|
//# sourceMappingURL=index.js.map
|