@absolutejs/ai 0.0.48 → 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 +122 -21
- package/dist/ai/client/index.js +19 -22
- package/dist/ai/client/index.js.map +7 -8
- package/dist/ai/index.js +703 -63
- package/dist/ai/index.js.map +20 -18
- 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/angular/ai/index.js +31 -3
- package/dist/angular/ai/index.js.map +6 -6
- package/dist/react/ai/index.js +18 -3
- package/dist/react/ai/index.js.map +6 -6
- package/dist/src/ai/client/actions.d.ts +22 -0
- package/dist/src/ai/client/createAIStream.d.ts +1 -0
- package/dist/src/ai/client/index.d.ts +0 -1
- package/dist/src/ai/conversationManager.d.ts +1 -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/src/angular/ai/ai-stream.service.d.ts +1 -0
- package/dist/src/react/ai/useAIStream.d.ts +1 -0
- package/dist/src/svelte/ai/createAIStream.d.ts +1 -0
- package/dist/src/vue/ai/useAIStream.d.ts +2 -0
- package/dist/svelte/ai/index.js +18 -3
- package/dist/svelte/ai/index.js.map +6 -6
- package/dist/types/ai.d.ts +74 -7
- package/dist/vue/ai/index.js +18 -3
- package/dist/vue/ai/index.js.map +6 -6
- package/package.json +9 -2
- package/dist/src/ai/client/composer.d.ts +0 -15
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
|
|
|
@@ -2382,6 +2961,7 @@ var isValidAIClientMessage = (data) => {
|
|
|
2382
2961
|
case "cancel":
|
|
2383
2962
|
return "conversationId" in data && typeof data.conversationId === "string";
|
|
2384
2963
|
case "branch":
|
|
2964
|
+
case "edit":
|
|
2385
2965
|
return "messageId" in data && typeof data.messageId === "string" && "content" in data && typeof data.content === "string" && "conversationId" in data && typeof data.conversationId === "string";
|
|
2386
2966
|
default:
|
|
2387
2967
|
return false;
|
|
@@ -2409,7 +2989,7 @@ var isValidAIServerMessage = (data) => {
|
|
|
2409
2989
|
case "turn_started":
|
|
2410
2990
|
return "conversationId" in data && typeof data.conversationId === "string" && "messageId" in data && typeof data.messageId === "string";
|
|
2411
2991
|
case "branched":
|
|
2412
|
-
return "content" in data && typeof data.content === "string" && "fromMessageId" in data && typeof data.fromMessageId === "string" && "messageId" in data && typeof data.messageId === "string" && "newConversationId" in data && typeof data.newConversationId === "string" && "oldConversationId" in data && typeof data.oldConversationId === "string";
|
|
2992
|
+
return "content" in data && typeof data.content === "string" && "fromMessageId" in data && typeof data.fromMessageId === "string" && "messageId" in data && typeof data.messageId === "string" && "newConversationId" in data && typeof data.newConversationId === "string" && "oldConversationId" in data && typeof data.oldConversationId === "string" && (!("mode" in data) || data.mode === "append" || data.mode === "replace");
|
|
2413
2993
|
case "rag_retrieved":
|
|
2414
2994
|
return "conversationId" in data && "messageId" in data && "sources" in data && Array.isArray(data.sources);
|
|
2415
2995
|
case "error":
|
|
@@ -2643,6 +3223,7 @@ var processToolTurn = async (socket, options, state, messageId, conversationId,
|
|
|
2643
3223
|
messages: state.currentMessages,
|
|
2644
3224
|
model: options.model,
|
|
2645
3225
|
promptCaching: options.promptCaching,
|
|
3226
|
+
providerOptions: options.providerOptions,
|
|
2646
3227
|
reasoning: options.reasoning,
|
|
2647
3228
|
signal,
|
|
2648
3229
|
systemPrompt: options.systemPrompt,
|
|
@@ -2729,6 +3310,7 @@ var processStream = async (socket, options, messages, messageId, conversationId,
|
|
|
2729
3310
|
messages,
|
|
2730
3311
|
model: options.model,
|
|
2731
3312
|
promptCaching: options.promptCaching,
|
|
3313
|
+
providerOptions: options.providerOptions,
|
|
2732
3314
|
reasoning: options.reasoning,
|
|
2733
3315
|
signal,
|
|
2734
3316
|
systemPrompt: options.systemPrompt,
|
|
@@ -3110,6 +3692,7 @@ var streamTurns = async function* (options, renderers, messages, signal, startTi
|
|
|
3110
3692
|
messages: turnState.currentMessages,
|
|
3111
3693
|
model: options.model,
|
|
3112
3694
|
promptCaching: options.promptCaching,
|
|
3695
|
+
providerOptions: options.providerOptions,
|
|
3113
3696
|
reasoning: options.reasoning,
|
|
3114
3697
|
signal,
|
|
3115
3698
|
systemPrompt: options.systemPrompt,
|
|
@@ -3122,6 +3705,13 @@ var streamTurns = async function* (options, renderers, messages, signal, startTi
|
|
|
3122
3705
|
aggregateUsage.outputTokens += chunkState.usage?.outputTokens ?? 0;
|
|
3123
3706
|
aggregateUsage.cacheReadInputTokens = (aggregateUsage.cacheReadInputTokens ?? 0) + (chunkState.usage?.cacheReadInputTokens ?? 0);
|
|
3124
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);
|
|
3125
3715
|
const runningTotalTokens = aggregateUsage.inputTokens + aggregateUsage.outputTokens;
|
|
3126
3716
|
if (chunkState.stopReason === "max_tokens") {
|
|
3127
3717
|
finishReason = "max_tokens";
|
|
@@ -3260,13 +3850,16 @@ var getHistory = (conversation) => conversation.messages.map((msg) => ({
|
|
|
3260
3850
|
content: msg.content,
|
|
3261
3851
|
role: msg.role
|
|
3262
3852
|
}));
|
|
3263
|
-
var branchConversation = (source, fromMessageId) => {
|
|
3853
|
+
var branchConversation = (source, fromMessageId, mode) => {
|
|
3264
3854
|
const cutoffIndex = source.messages.findIndex((msg) => msg.id === fromMessageId);
|
|
3265
3855
|
if (cutoffIndex === NOT_FOUND2) {
|
|
3266
3856
|
return null;
|
|
3267
3857
|
}
|
|
3858
|
+
if (mode === "replace" && source.messages[cutoffIndex]?.role !== "user") {
|
|
3859
|
+
return null;
|
|
3860
|
+
}
|
|
3268
3861
|
const newId = generateId();
|
|
3269
|
-
const branchedMessages = source.messages.slice(0, cutoffIndex + 1).map((msg) => ({ ...msg, conversationId: newId }));
|
|
3862
|
+
const branchedMessages = source.messages.slice(0, cutoffIndex + (mode === "replace" ? 0 : 1)).map((msg) => ({ ...msg, conversationId: newId }));
|
|
3270
3863
|
const newConversation = {
|
|
3271
3864
|
createdAt: Date.now(),
|
|
3272
3865
|
id: newId,
|
|
@@ -3338,24 +3931,28 @@ var aiChat = (config2) => {
|
|
|
3338
3931
|
abortControllers.delete(conversationId);
|
|
3339
3932
|
}
|
|
3340
3933
|
};
|
|
3341
|
-
const handleBranch = async (ws, messageId, conversationId, content) => {
|
|
3934
|
+
const handleBranch = async (ws, messageId, conversationId, content, mode) => {
|
|
3342
3935
|
const source = await store.get(conversationId);
|
|
3343
3936
|
if (!source) {
|
|
3344
3937
|
return;
|
|
3345
3938
|
}
|
|
3346
|
-
const
|
|
3939
|
+
const editedAttachments = mode === "replace" ? source.messages.find(({ id }) => id === messageId)?.attachments : undefined;
|
|
3940
|
+
const newConv = branchConversation(source, messageId, mode);
|
|
3347
3941
|
if (newConv) {
|
|
3348
3942
|
await store.set(newConv.id, newConv);
|
|
3349
3943
|
const clientMessageId = generateId();
|
|
3350
3944
|
sendServerEvent(ws, {
|
|
3945
|
+
attachments: editedAttachments,
|
|
3351
3946
|
content,
|
|
3352
3947
|
fromMessageId: messageId,
|
|
3353
3948
|
messageId: clientMessageId,
|
|
3949
|
+
mode,
|
|
3354
3950
|
newConversationId: newConv.id,
|
|
3355
3951
|
oldConversationId: conversationId,
|
|
3356
3952
|
type: "branched"
|
|
3357
3953
|
});
|
|
3358
3954
|
enqueueUserMessage({
|
|
3955
|
+
attachments: editedAttachments,
|
|
3359
3956
|
clientMessageId,
|
|
3360
3957
|
content,
|
|
3361
3958
|
conversationId: newConv.id,
|
|
@@ -3555,7 +4152,11 @@ var aiChat = (config2) => {
|
|
|
3555
4152
|
return;
|
|
3556
4153
|
}
|
|
3557
4154
|
if (msg.type === "branch") {
|
|
3558
|
-
await handleBranch(ws, msg.messageId, msg.conversationId, msg.content);
|
|
4155
|
+
await handleBranch(ws, msg.messageId, msg.conversationId, msg.content, "append");
|
|
4156
|
+
return;
|
|
4157
|
+
}
|
|
4158
|
+
if (msg.type === "edit") {
|
|
4159
|
+
await handleBranch(ws, msg.messageId, msg.conversationId, msg.content, "replace");
|
|
3559
4160
|
return;
|
|
3560
4161
|
}
|
|
3561
4162
|
if (msg.type === "message") {
|
|
@@ -3594,6 +4195,7 @@ var generateAI = async (options) => {
|
|
|
3594
4195
|
messages: options.messages,
|
|
3595
4196
|
model: options.model,
|
|
3596
4197
|
promptCaching: options.promptCaching,
|
|
4198
|
+
providerOptions: options.providerOptions,
|
|
3597
4199
|
reasoning: options.reasoning,
|
|
3598
4200
|
responseFormat: options.responseFormat,
|
|
3599
4201
|
signal: options.signal,
|
|
@@ -3606,17 +4208,22 @@ var generateAI = async (options) => {
|
|
|
3606
4208
|
});
|
|
3607
4209
|
let text = "";
|
|
3608
4210
|
const toolCalls = [];
|
|
4211
|
+
const citations = [];
|
|
3609
4212
|
let usage;
|
|
4213
|
+
let metadata;
|
|
3610
4214
|
for await (const chunk of stream) {
|
|
3611
4215
|
if (chunk.type === "text") {
|
|
3612
4216
|
text += chunk.content;
|
|
3613
4217
|
} else if (chunk.type === "tool_use") {
|
|
3614
4218
|
toolCalls.push({ id: chunk.id, input: chunk.input, name: chunk.name });
|
|
4219
|
+
} else if (chunk.type === "citation") {
|
|
4220
|
+
citations.push(chunk);
|
|
3615
4221
|
} else if (chunk.type === "done") {
|
|
3616
4222
|
usage = chunk.usage;
|
|
4223
|
+
metadata = chunk.metadata;
|
|
3617
4224
|
}
|
|
3618
4225
|
}
|
|
3619
|
-
return { text, toolCalls, usage };
|
|
4226
|
+
return { citations, metadata, text, toolCalls, usage };
|
|
3620
4227
|
};
|
|
3621
4228
|
var DEFAULT_TOOL_MAX_TURNS = 6;
|
|
3622
4229
|
var mergeUsage = (left, right) => {
|
|
@@ -3625,11 +4232,18 @@ var mergeUsage = (left, right) => {
|
|
|
3625
4232
|
if (!right)
|
|
3626
4233
|
return left;
|
|
3627
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;
|
|
3628
4238
|
return {
|
|
3629
4239
|
cacheReadInputTokens: add(left.cacheReadInputTokens, right.cacheReadInputTokens),
|
|
3630
4240
|
cacheWriteInputTokens: add(left.cacheWriteInputTokens, right.cacheWriteInputTokens),
|
|
4241
|
+
costCredits: add(left.costCredits, right.costCredits),
|
|
3631
4242
|
inputTokens: (left.inputTokens ?? 0) + (right.inputTokens ?? 0),
|
|
3632
|
-
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)
|
|
3633
4247
|
};
|
|
3634
4248
|
};
|
|
3635
4249
|
var toProviderTools = (tools) => Object.entries(tools).map(([name, definition]) => ({
|
|
@@ -3813,6 +4427,7 @@ var streamAIWithTools = async function* (options) {
|
|
|
3813
4427
|
messages,
|
|
3814
4428
|
model: base.model,
|
|
3815
4429
|
promptCaching: base.promptCaching,
|
|
4430
|
+
providerOptions: base.providerOptions,
|
|
3816
4431
|
reasoning: base.reasoning,
|
|
3817
4432
|
signal: base.signal,
|
|
3818
4433
|
stopSequences: base.stopSequences,
|
|
@@ -5211,7 +5826,7 @@ var createConversationManager = () => {
|
|
|
5211
5826
|
conversation.title = message.content.slice(0, TITLE_MAX_LENGTH2);
|
|
5212
5827
|
}
|
|
5213
5828
|
};
|
|
5214
|
-
const
|
|
5829
|
+
const fork = (fromMessageId, sourceConversationId, mode) => {
|
|
5215
5830
|
const source = conversations.get(sourceConversationId);
|
|
5216
5831
|
if (!source) {
|
|
5217
5832
|
return null;
|
|
@@ -5220,8 +5835,11 @@ var createConversationManager = () => {
|
|
|
5220
5835
|
if (cutoffIndex === NOT_FOUND3) {
|
|
5221
5836
|
return null;
|
|
5222
5837
|
}
|
|
5838
|
+
if (mode === "replace" && source.messages[cutoffIndex]?.role !== "user") {
|
|
5839
|
+
return null;
|
|
5840
|
+
}
|
|
5223
5841
|
const newId = generateId();
|
|
5224
|
-
const branchedMessages = source.messages.slice(0, cutoffIndex + 1).map((msg) => ({ ...msg, conversationId: newId }));
|
|
5842
|
+
const branchedMessages = source.messages.slice(0, cutoffIndex + (mode === "replace" ? 0 : 1)).map((msg) => ({ ...msg, conversationId: newId }));
|
|
5225
5843
|
const newConversation = {
|
|
5226
5844
|
createdAt: Date.now(),
|
|
5227
5845
|
id: newId,
|
|
@@ -5230,6 +5848,8 @@ var createConversationManager = () => {
|
|
|
5230
5848
|
conversations.set(newId, newConversation);
|
|
5231
5849
|
return newId;
|
|
5232
5850
|
};
|
|
5851
|
+
const branch = (fromMessageId, sourceConversationId) => fork(fromMessageId, sourceConversationId, "append");
|
|
5852
|
+
const edit = (messageId, sourceConversationId) => fork(messageId, sourceConversationId, "replace");
|
|
5233
5853
|
const emptyHistory = [];
|
|
5234
5854
|
const getHistory2 = (conversationId) => {
|
|
5235
5855
|
const conversation = conversations.get(conversationId);
|
|
@@ -5275,6 +5895,7 @@ var createConversationManager = () => {
|
|
|
5275
5895
|
abort,
|
|
5276
5896
|
appendMessage: appendMessage2,
|
|
5277
5897
|
branch,
|
|
5898
|
+
edit,
|
|
5278
5899
|
get,
|
|
5279
5900
|
getAbortController,
|
|
5280
5901
|
getHistory: getHistory2,
|
|
@@ -5413,9 +6034,11 @@ var serverMessageToAction = (message) => {
|
|
|
5413
6034
|
};
|
|
5414
6035
|
case "branched":
|
|
5415
6036
|
return {
|
|
6037
|
+
attachments: message.attachments,
|
|
5416
6038
|
content: message.content,
|
|
5417
6039
|
fromMessageId: message.fromMessageId,
|
|
5418
6040
|
messageId: message.messageId,
|
|
6041
|
+
mode: message.mode,
|
|
5419
6042
|
newConversationId: message.newConversationId,
|
|
5420
6043
|
oldConversationId: message.oldConversationId,
|
|
5421
6044
|
type: "branch"
|
|
@@ -5819,13 +6442,15 @@ var handleBranch = (state, action) => {
|
|
|
5819
6442
|
if (cutoffIndex < 0) {
|
|
5820
6443
|
return;
|
|
5821
6444
|
}
|
|
5822
|
-
const
|
|
6445
|
+
const cutoffOffset = action.mode === "replace" ? 0 : 1;
|
|
6446
|
+
const branchedMessages = source.messages.slice(0, cutoffIndex + cutoffOffset).map((msg) => ({ ...msg, conversationId: action.newConversationId }));
|
|
5823
6447
|
const newConversation = {
|
|
5824
6448
|
createdAt: Date.now(),
|
|
5825
6449
|
id: action.newConversationId,
|
|
5826
6450
|
messages: [
|
|
5827
6451
|
...branchedMessages,
|
|
5828
6452
|
{
|
|
6453
|
+
attachments: action.attachments,
|
|
5829
6454
|
content: action.content,
|
|
5830
6455
|
conversationId: action.newConversationId,
|
|
5831
6456
|
id: action.messageId,
|
|
@@ -5943,6 +6568,16 @@ var createAIStream = (path, conversationId) => {
|
|
|
5943
6568
|
});
|
|
5944
6569
|
}
|
|
5945
6570
|
};
|
|
6571
|
+
const edit = (messageId, content) => {
|
|
6572
|
+
if (activeConversationId) {
|
|
6573
|
+
connection.send({
|
|
6574
|
+
content,
|
|
6575
|
+
conversationId: activeConversationId,
|
|
6576
|
+
messageId,
|
|
6577
|
+
type: "edit"
|
|
6578
|
+
});
|
|
6579
|
+
}
|
|
6580
|
+
};
|
|
5946
6581
|
const cancel = () => {
|
|
5947
6582
|
if (activeConversationId) {
|
|
5948
6583
|
store.dispatch({ type: "cancel" });
|
|
@@ -5986,6 +6621,7 @@ var createAIStream = (path, conversationId) => {
|
|
|
5986
6621
|
branch,
|
|
5987
6622
|
cancel,
|
|
5988
6623
|
destroy,
|
|
6624
|
+
edit,
|
|
5989
6625
|
send,
|
|
5990
6626
|
subscribe,
|
|
5991
6627
|
get error() {
|
|
@@ -6190,9 +6826,12 @@ export {
|
|
|
6190
6826
|
parseChoiceSpec,
|
|
6191
6827
|
parseChartSpec,
|
|
6192
6828
|
parseAIMessage,
|
|
6829
|
+
openrouterResponses,
|
|
6830
|
+
openrouter,
|
|
6193
6831
|
openaiResponses,
|
|
6194
6832
|
openaiCompatible,
|
|
6195
6833
|
openai,
|
|
6834
|
+
openRouterModelMatchesRule,
|
|
6196
6835
|
ollama,
|
|
6197
6836
|
moonshot,
|
|
6198
6837
|
mistralai,
|
|
@@ -6212,6 +6851,7 @@ export {
|
|
|
6212
6851
|
createUiCards,
|
|
6213
6852
|
createSyncConversationStore,
|
|
6214
6853
|
createProviderProxyResponse,
|
|
6854
|
+
createOpenRouterClient,
|
|
6215
6855
|
createOAuth2ClientCredentialsTokenSource,
|
|
6216
6856
|
createMemoryStore,
|
|
6217
6857
|
createConversationManager,
|
|
@@ -6249,5 +6889,5 @@ export {
|
|
|
6249
6889
|
BUILTIN_UI_CARDS
|
|
6250
6890
|
};
|
|
6251
6891
|
|
|
6252
|
-
//# debugId=
|
|
6892
|
+
//# debugId=A06FECA819C53C2464756E2164756E21
|
|
6253
6893
|
//# sourceMappingURL=index.js.map
|