@byte_fluffy/nexra-sdk 0.1.0-alpha.46 → 0.1.0-alpha.47
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 +86 -0
- package/dist/catalog.js +1 -1
- package/dist/{chunk-ZNPLN7N6.js → chunk-7MNUV5BJ.js} +4623 -415
- package/dist/index.js +1 -1
- package/dist/language.js +240 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
package/dist/language.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
jsonBody,
|
|
7
7
|
parseServerSentEvents,
|
|
8
8
|
resolveNexraLanguageReasoning
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7MNUV5BJ.js";
|
|
10
10
|
import {
|
|
11
11
|
isPublicErrorCode,
|
|
12
12
|
publicErrorMessage,
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
18
18
|
import { getModelCapabilities as getAnthropicModelCapabilities } from "@ai-sdk/anthropic/internal";
|
|
19
19
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
20
|
-
import { createOpenAI } from "@ai-sdk/openai";
|
|
21
20
|
import { mapReasoningToProviderBudget } from "@ai-sdk/provider-utils";
|
|
22
21
|
|
|
23
22
|
// src/language/chat-model.ts
|
|
@@ -72,6 +71,30 @@ function nativeOptions(part) {
|
|
|
72
71
|
const value = part.providerOptions?.nexra;
|
|
73
72
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
74
73
|
}
|
|
74
|
+
function nativeMediaUrlPatterns(binding, kind) {
|
|
75
|
+
const field = kind === "audio" ? "input_audio" : `${kind}_url`;
|
|
76
|
+
const messages2 = asObject(asObject(binding.endpoint.inputSchema?.properties).messages);
|
|
77
|
+
const content = asObject(asObject(asObject(messages2.items).properties).content);
|
|
78
|
+
const patterns = /* @__PURE__ */ new Set();
|
|
79
|
+
const visit = (value) => {
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
value.forEach(visit);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const node = asObject(value);
|
|
85
|
+
const properties = asObject(node.properties);
|
|
86
|
+
if (asObject(properties.type).const === field) {
|
|
87
|
+
const url = asObject(asObject(asObject(properties[field]).properties).url);
|
|
88
|
+
if (typeof url.pattern === "string") patterns.add(url.pattern);
|
|
89
|
+
}
|
|
90
|
+
for (const child of Object.values(node)) if (child && typeof child === "object") visit(child);
|
|
91
|
+
};
|
|
92
|
+
visit(content);
|
|
93
|
+
return patterns.size ? [...patterns].map((pattern) => new RegExp(pattern)) : void 0;
|
|
94
|
+
}
|
|
95
|
+
function chatSupportedUrls(binding) {
|
|
96
|
+
return Object.fromEntries(binding.spec.capabilities.inputModalities.filter((kind) => ["image", "audio", "video"].includes(kind)).map((kind) => [`${kind}/*`, nativeMediaUrlPatterns(binding, kind) ?? [/^https?:\/\//]]));
|
|
97
|
+
}
|
|
75
98
|
function filePart(part, binding) {
|
|
76
99
|
const kind = part.mediaType.split("/")[0];
|
|
77
100
|
if (["image", "audio", "video"].includes(kind) && !binding.spec.capabilities.inputModalities.includes(kind)) {
|
|
@@ -83,6 +106,10 @@ function filePart(part, binding) {
|
|
|
83
106
|
throw new UnsupportedFunctionalityError({ functionality: "file reference without a Nexra reference" });
|
|
84
107
|
}
|
|
85
108
|
const url = part.data.type === "url" ? part.data.url.toString() : void 0;
|
|
109
|
+
const patterns = nativeMediaUrlPatterns(binding, kind);
|
|
110
|
+
if (url && patterns && !patterns.some((pattern) => pattern.test(url))) {
|
|
111
|
+
throw new UnsupportedFunctionalityError({ functionality: `native media URL for ${binding.spec.id}; provide bytes or a matching data URL` });
|
|
112
|
+
}
|
|
86
113
|
const data = part.data.type === "data" ? convertToBase64(part.data.data) : void 0;
|
|
87
114
|
if (kind === "image" || kind === "video") {
|
|
88
115
|
const field = kind === "image" ? "image_url" : "video_url";
|
|
@@ -380,7 +407,7 @@ function createChatLanguageModel(binding, transport) {
|
|
|
380
407
|
specificationVersion: "v4",
|
|
381
408
|
provider: "nexra.chat",
|
|
382
409
|
modelId: binding.spec.id,
|
|
383
|
-
supportedUrls:
|
|
410
|
+
supportedUrls: chatSupportedUrls(binding),
|
|
384
411
|
async doGenerate(options) {
|
|
385
412
|
const body = chatRequest(binding, options, false);
|
|
386
413
|
const response = await transport.response("/chat/completions", { method: "POST", ...jsonBody(body), headers: combineHeaders(options), signal: options.abortSignal }, { retryable: false });
|
|
@@ -528,6 +555,188 @@ function combineHeaders(options) {
|
|
|
528
555
|
return headers;
|
|
529
556
|
}
|
|
530
557
|
|
|
558
|
+
// src/language/responses-model.ts
|
|
559
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
560
|
+
function object3(value) {
|
|
561
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
562
|
+
}
|
|
563
|
+
function isUnpositionedCitation(value) {
|
|
564
|
+
const node = object3(value);
|
|
565
|
+
return node.type === "url_citation" && typeof node.url === "string" && !Object.hasOwn(node, "start_index") && !Object.hasOwn(node, "end_index");
|
|
566
|
+
}
|
|
567
|
+
function project(value) {
|
|
568
|
+
if (Array.isArray(value)) return value.map(project);
|
|
569
|
+
if (!value || typeof value !== "object") return value;
|
|
570
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
571
|
+
key,
|
|
572
|
+
key === "annotations" && Array.isArray(item) ? item.filter((annotation) => !isUnpositionedCitation(annotation)).map(project) : project(item)
|
|
573
|
+
]));
|
|
574
|
+
}
|
|
575
|
+
function citations(value) {
|
|
576
|
+
if (Array.isArray(value)) return value.flatMap(citations);
|
|
577
|
+
const node = object3(value);
|
|
578
|
+
if (isUnpositionedCitation(node)) return [node];
|
|
579
|
+
return Object.values(node).flatMap((item) => item && typeof item === "object" ? citations(item) : []);
|
|
580
|
+
}
|
|
581
|
+
function nativeSearchBody(value, call) {
|
|
582
|
+
const extension = call.providerOptions?.nexra?.web_search;
|
|
583
|
+
if (extension === void 0) return value;
|
|
584
|
+
if (!extension || typeof extension !== "object" || Array.isArray(extension)) throw new Error("Nexra web_search options must be a native tool object");
|
|
585
|
+
const body = object3(value);
|
|
586
|
+
if (!Array.isArray(body.tools) || !body.tools.some((tool) => object3(tool).type === "web_search")) {
|
|
587
|
+
throw new Error("Nexra web_search options require an enabled web_search tool");
|
|
588
|
+
}
|
|
589
|
+
return { ...body, tools: body.tools.map((tool) => object3(tool).type === "web_search" ? { ...object3(tool), ...extension, type: "web_search" } : tool) };
|
|
590
|
+
}
|
|
591
|
+
function nativeSearchRequest(init, call) {
|
|
592
|
+
return typeof init?.body === "string" ? { ...init, body: JSON.stringify(nativeSearchBody(JSON.parse(init.body), call)) } : init;
|
|
593
|
+
}
|
|
594
|
+
function createResponsesLanguageModel(options) {
|
|
595
|
+
const factory = (fetch) => createOpenAI({
|
|
596
|
+
apiKey: options.apiKey,
|
|
597
|
+
headers: options.headers,
|
|
598
|
+
name: "nexra.openai",
|
|
599
|
+
baseURL: options.baseUrl,
|
|
600
|
+
fetch
|
|
601
|
+
}).responses(options.modelId);
|
|
602
|
+
const base = factory(options.fetch);
|
|
603
|
+
return {
|
|
604
|
+
specificationVersion: "v4",
|
|
605
|
+
provider: base.provider,
|
|
606
|
+
modelId: base.modelId,
|
|
607
|
+
supportedUrls: base.supportedUrls,
|
|
608
|
+
async doGenerate(call) {
|
|
609
|
+
let original;
|
|
610
|
+
const fetch = async (input, init) => {
|
|
611
|
+
const response = await options.fetch(input, nativeSearchRequest(init, call));
|
|
612
|
+
if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) return response;
|
|
613
|
+
const text = await response.text();
|
|
614
|
+
try {
|
|
615
|
+
original = JSON.parse(text);
|
|
616
|
+
} catch {
|
|
617
|
+
return new Response(text, response);
|
|
618
|
+
}
|
|
619
|
+
return new Response(JSON.stringify(project(original)), response);
|
|
620
|
+
};
|
|
621
|
+
const result = await factory(fetch).doGenerate(call);
|
|
622
|
+
const seen = /* @__PURE__ */ new Set();
|
|
623
|
+
const sources = sourceParts(original, seen);
|
|
624
|
+
return {
|
|
625
|
+
...result,
|
|
626
|
+
content: [...result.content, ...sources],
|
|
627
|
+
request: result.request ? { ...result.request, body: nativeSearchBody(result.request.body, call) } : void 0,
|
|
628
|
+
response: { ...result.response, ...original === void 0 ? {} : { body: original } }
|
|
629
|
+
};
|
|
630
|
+
},
|
|
631
|
+
async doStream(call) {
|
|
632
|
+
const entries = /* @__PURE__ */ new Map();
|
|
633
|
+
let before = [];
|
|
634
|
+
const canceled = new AbortController();
|
|
635
|
+
const fetch = async (input, init) => {
|
|
636
|
+
const response = await options.fetch(input, nativeSearchRequest(init, call));
|
|
637
|
+
if (!response.ok || !response.headers.get("content-type")?.includes("text/event-stream")) return response;
|
|
638
|
+
const signal = init?.signal ? AbortSignal.any([init.signal, canceled.signal]) : canceled.signal;
|
|
639
|
+
const iterator = parseServerSentEvents(response, { signal });
|
|
640
|
+
const encoder = new TextEncoder();
|
|
641
|
+
const body = new ReadableStream({
|
|
642
|
+
async pull(controller) {
|
|
643
|
+
try {
|
|
644
|
+
while (true) {
|
|
645
|
+
const next = await iterator.next();
|
|
646
|
+
if (next.done) {
|
|
647
|
+
controller.close();
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
const frame = next.value;
|
|
651
|
+
let native;
|
|
652
|
+
try {
|
|
653
|
+
native = JSON.parse(frame.data);
|
|
654
|
+
} catch {
|
|
655
|
+
controller.enqueue(encoder.encode(`${frame.event ? `event: ${frame.event}
|
|
656
|
+
` : ""}data: ${frame.data}
|
|
657
|
+
|
|
658
|
+
`));
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (object3(native).type === "response.output_text.annotation.added" && isUnpositionedCitation(object3(native).annotation)) {
|
|
662
|
+
before.push(native);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
const projected = project(native);
|
|
666
|
+
const key = JSON.stringify(projected);
|
|
667
|
+
const queue = entries.get(key) ?? [];
|
|
668
|
+
queue.push({ original: native, before });
|
|
669
|
+
before = [];
|
|
670
|
+
entries.set(key, queue);
|
|
671
|
+
controller.enqueue(encoder.encode(`${frame.id ? `id: ${frame.id}
|
|
672
|
+
` : ""}${frame.event ? `event: ${frame.event}
|
|
673
|
+
` : ""}${frame.retry !== void 0 ? `retry: ${frame.retry}
|
|
674
|
+
` : ""}data: ${key}
|
|
675
|
+
|
|
676
|
+
`));
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
} catch (error) {
|
|
680
|
+
controller.error(error);
|
|
681
|
+
await iterator.return(void 0);
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
async cancel(reason) {
|
|
685
|
+
canceled.abort(reason);
|
|
686
|
+
await iterator.return(void 0);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
return new Response(body, response);
|
|
690
|
+
};
|
|
691
|
+
const result = await factory(fetch).doStream({ ...call, includeRawChunks: true });
|
|
692
|
+
const seen = /* @__PURE__ */ new Set();
|
|
693
|
+
return {
|
|
694
|
+
...result,
|
|
695
|
+
request: result.request ? { ...result.request, body: nativeSearchBody(result.request.body, call) } : void 0,
|
|
696
|
+
stream: result.stream.pipeThrough(new TransformStream({
|
|
697
|
+
transform(part, controller) {
|
|
698
|
+
if (part.type !== "raw") {
|
|
699
|
+
controller.enqueue(part);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const key = JSON.stringify(part.rawValue);
|
|
703
|
+
const queue = entries.get(key);
|
|
704
|
+
const entry = queue?.shift();
|
|
705
|
+
if (queue?.length === 0) entries.delete(key);
|
|
706
|
+
const native = entry?.original ?? part.rawValue;
|
|
707
|
+
for (const frame of [...entry?.before ?? [], native]) {
|
|
708
|
+
for (const source of sourceParts(frame, seen)) controller.enqueue(source);
|
|
709
|
+
if (call.includeRawChunks) controller.enqueue({ type: "raw", rawValue: frame });
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
flush(controller) {
|
|
713
|
+
for (const frame of before) {
|
|
714
|
+
for (const source of sourceParts(frame, seen)) controller.enqueue(source);
|
|
715
|
+
if (call.includeRawChunks) controller.enqueue({ type: "raw", rawValue: frame });
|
|
716
|
+
}
|
|
717
|
+
entries.clear();
|
|
718
|
+
before = [];
|
|
719
|
+
}
|
|
720
|
+
}))
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
function sourceParts(value, seen) {
|
|
726
|
+
return citations(value).flatMap((annotation) => {
|
|
727
|
+
const key = JSON.stringify([annotation.url, annotation.title]);
|
|
728
|
+
if (seen.has(key)) return [];
|
|
729
|
+
seen.add(key);
|
|
730
|
+
return [{
|
|
731
|
+
type: "source",
|
|
732
|
+
sourceType: "url",
|
|
733
|
+
id: `native-source-${seen.size}`,
|
|
734
|
+
url: annotation.url,
|
|
735
|
+
...typeof annotation.title === "string" ? { title: annotation.title } : {}
|
|
736
|
+
}];
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
531
740
|
// src/language/index.ts
|
|
532
741
|
function createNexraLanguageModel(modelId, options) {
|
|
533
742
|
const binding = getLanguageModelBinding(modelId, options);
|
|
@@ -553,30 +762,30 @@ function createNexraLanguageModel(modelId, options) {
|
|
|
553
762
|
input,
|
|
554
763
|
typeof init?.body === "string" ? { ...init, body: JSON.stringify(explicitResponsesMessages(JSON.parse(init.body))) } : init
|
|
555
764
|
);
|
|
556
|
-
model =
|
|
765
|
+
model = createResponsesLanguageModel({ modelId: binding.spec.id, apiKey, headers: common.headers, baseUrl: `${root}/v1`, fetch: responsesFetch });
|
|
557
766
|
} else {
|
|
558
767
|
model = createAnthropic({ ...common, name: "nexra.anthropic", baseURL: `${root}/v1` })(binding.spec.id);
|
|
559
768
|
}
|
|
560
769
|
return withNativeContract(model, binding);
|
|
561
770
|
}
|
|
562
|
-
function
|
|
771
|
+
function object4(value) {
|
|
563
772
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
564
773
|
}
|
|
565
774
|
function explicitResponsesMessages(value) {
|
|
566
|
-
const body =
|
|
775
|
+
const body = object4(value);
|
|
567
776
|
if (!Array.isArray(body.input)) return value;
|
|
568
777
|
return { ...body, input: body.input.map((value2) => {
|
|
569
|
-
const item =
|
|
778
|
+
const item = object4(value2);
|
|
570
779
|
return item.type === void 0 && ["system", "developer", "user", "assistant"].includes(String(item.role)) && (typeof item.content === "string" || Array.isArray(item.content)) ? { ...item, type: "message" } : value2;
|
|
571
780
|
}) };
|
|
572
781
|
}
|
|
573
782
|
function googleNativeBody(binding, value) {
|
|
574
|
-
const body =
|
|
575
|
-
const config =
|
|
576
|
-
const thinking =
|
|
577
|
-
const fields =
|
|
578
|
-
const nativeThinking =
|
|
579
|
-
const values =
|
|
783
|
+
const body = object4(value);
|
|
784
|
+
const config = object4(body.generationConfig);
|
|
785
|
+
const thinking = object4(config.thinkingConfig);
|
|
786
|
+
const fields = object4(binding.endpoint.inputSchema?.properties);
|
|
787
|
+
const nativeThinking = object4(object4(object4(object4(fields.generationConfig).properties).thinkingConfig).properties);
|
|
788
|
+
const values = object4(nativeThinking.thinkingLevel).enum;
|
|
580
789
|
const level = typeof thinking.thinkingLevel === "string" && Array.isArray(values) ? values.find((value2) => typeof value2 === "string" && value2.toLowerCase() === String(thinking.thinkingLevel).toLowerCase()) : void 0;
|
|
581
790
|
return level === void 0 || level === thinking.thinkingLevel ? value : { ...body, generationConfig: { ...config, thinkingConfig: { ...thinking, thinkingLevel: level } } };
|
|
582
791
|
}
|
|
@@ -584,25 +793,29 @@ function nativeRequest(binding, request, stream = false) {
|
|
|
584
793
|
if (!request) return request;
|
|
585
794
|
return binding.endpoint.protocol === "openai-responses" ? {
|
|
586
795
|
...request,
|
|
587
|
-
body: explicitResponsesMessages(stream ? { ...
|
|
796
|
+
body: explicitResponsesMessages(stream ? { ...object4(request.body), stream: true } : request.body)
|
|
588
797
|
} : binding.endpoint.protocol === "gemini-generate-content" ? { ...request, body: googleNativeBody(binding, request.body) } : request;
|
|
589
798
|
}
|
|
590
799
|
function prepare(options, binding, stream) {
|
|
591
800
|
binding = getLanguageModelBinding(binding.spec.id, { protocol: binding.endpoint.protocol, stream });
|
|
592
801
|
options = prepareLanguageReasoning(options, binding);
|
|
593
|
-
const properties =
|
|
594
|
-
const
|
|
802
|
+
const properties = object4(binding.endpoint.inputSchema?.properties);
|
|
803
|
+
const choices2 = object4(properties.tool_choice).enum;
|
|
804
|
+
if (options.toolChoice && Array.isArray(choices2) && !choices2.includes(options.toolChoice.type)) {
|
|
805
|
+
throw new Error(`Nexra tool choice is outside the native contract for ${binding.spec.id}: ${options.toolChoice.type}`);
|
|
806
|
+
}
|
|
807
|
+
const tokenField = object4(binding.endpoint.protocol === "gemini-generate-content" ? object4(object4(properties.generationConfig).properties).maxOutputTokens : properties.max_output_tokens ?? properties.max_tokens);
|
|
595
808
|
if (options.maxOutputTokens !== void 0 && (!Number.isInteger(options.maxOutputTokens) || typeof tokenField.minimum === "number" && options.maxOutputTokens < tokenField.minimum || binding.maxOutputTokens !== void 0 && options.maxOutputTokens > binding.maxOutputTokens)) throw new Error(`Nexra output token limit is outside the native contract for ${binding.spec.id}`);
|
|
596
809
|
if (options.tools?.length && !binding.endpoint.capabilities.includes("tool_call")) throw new Error(`Nexra model does not support tools: ${binding.spec.id}`);
|
|
597
810
|
const providerOptions = { ...options.providerOptions };
|
|
598
811
|
if (binding.endpoint.protocol === "openai-responses") {
|
|
599
|
-
const current =
|
|
812
|
+
const current = object4(providerOptions.openai);
|
|
600
813
|
providerOptions.openai = {
|
|
601
814
|
store: false,
|
|
602
815
|
forceReasoning: binding.endpoint.capabilities.includes("reasoning"),
|
|
603
816
|
...current
|
|
604
817
|
};
|
|
605
|
-
const reasoning =
|
|
818
|
+
const reasoning = object4(providerOptions.openai);
|
|
606
819
|
if (reasoning.forceReasoning === true && (options.temperature !== void 0 || options.topP !== void 0)) {
|
|
607
820
|
throw new Error(`Nexra Responses adapter cannot preserve explicit sampling parameters with reasoning for ${binding.spec.id}`);
|
|
608
821
|
}
|
|
@@ -612,8 +825,8 @@ function prepare(options, binding, stream) {
|
|
|
612
825
|
}
|
|
613
826
|
if (binding.endpoint.protocol === "anthropic-messages") {
|
|
614
827
|
const capabilities = getAnthropicModelCapabilities(binding.spec.id);
|
|
615
|
-
const native = { ...
|
|
616
|
-
let thinking =
|
|
828
|
+
const native = { ...object4(providerOptions.anthropic), ...object4(providerOptions.nexra) };
|
|
829
|
+
let thinking = object4(native.thinking);
|
|
617
830
|
if (!native.thinking && native.effort == null && options.reasoning && options.reasoning !== "provider-default") {
|
|
618
831
|
thinking = options.reasoning === "none" ? { type: "disabled" } : capabilities.supportsAdaptiveThinking ? { type: "adaptive" } : { type: "enabled", budgetTokens: mapReasoningToProviderBudget({
|
|
619
832
|
reasoning: options.reasoning,
|
|
@@ -634,20 +847,20 @@ function prepare(options, binding, stream) {
|
|
|
634
847
|
options = { ...options, maxOutputTokens: visible };
|
|
635
848
|
}
|
|
636
849
|
if (stream && binding.endpoint.protocol === "gemini-generate-content" && binding.endpoint.capabilities.includes("reasoning")) {
|
|
637
|
-
const google =
|
|
638
|
-
providerOptions.google = { ...google, thinkingConfig: { includeThoughts: true, ...
|
|
850
|
+
const google = object4(providerOptions.google);
|
|
851
|
+
providerOptions.google = { ...google, thinkingConfig: { includeThoughts: true, ...object4(google.thinkingConfig) } };
|
|
639
852
|
}
|
|
640
853
|
return { ...options, providerOptions };
|
|
641
854
|
}
|
|
642
855
|
function nativeUsage(protocol, value) {
|
|
643
|
-
const payload =
|
|
644
|
-
const raw = protocol === "gemini-generate-content" ? payload.usageMetadata : protocol === "anthropic-messages" ?
|
|
856
|
+
const payload = object4(value);
|
|
857
|
+
const raw = protocol === "gemini-generate-content" ? payload.usageMetadata : protocol === "anthropic-messages" ? object4(payload.message).usage ?? payload.usage : object4(payload.response).usage ?? payload.usage;
|
|
645
858
|
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0;
|
|
646
859
|
}
|
|
647
860
|
function mergeRecords(previous, next) {
|
|
648
861
|
const merged = { ...previous, ...next };
|
|
649
862
|
for (const [key, value] of Object.entries(next)) {
|
|
650
|
-
if (value && typeof value === "object" && !Array.isArray(value)) merged[key] = { ...
|
|
863
|
+
if (value && typeof value === "object" && !Array.isArray(value)) merged[key] = { ...object4(previous?.[key]), ...object4(value) };
|
|
651
864
|
}
|
|
652
865
|
return merged;
|
|
653
866
|
}
|
|
@@ -681,8 +894,8 @@ function withNativeContract(model, binding) {
|
|
|
681
894
|
stream: result.stream.pipeThrough(new TransformStream({
|
|
682
895
|
transform(part, controller) {
|
|
683
896
|
if (part.type === "raw") {
|
|
684
|
-
const payload =
|
|
685
|
-
terminal ||= binding.endpoint.protocol === "anthropic-messages" ? payload.type === "message_stop" : binding.endpoint.protocol === "openai-responses" ? ["response.completed", "response.incomplete", "response.failed"].includes(String(payload.type)) : Array.isArray(payload.candidates) && payload.candidates.some((candidate) => typeof
|
|
897
|
+
const payload = object4(part.rawValue);
|
|
898
|
+
terminal ||= binding.endpoint.protocol === "anthropic-messages" ? payload.type === "message_stop" : binding.endpoint.protocol === "openai-responses" ? ["response.completed", "response.incomplete", "response.failed"].includes(String(payload.type)) : Array.isArray(payload.candidates) && payload.candidates.some((candidate) => typeof object4(candidate).finishReason === "string");
|
|
686
899
|
const next = nativeUsage(binding.endpoint.protocol, part.rawValue);
|
|
687
900
|
if (next) raw = mergeRecords(raw, next);
|
|
688
901
|
if (options.includeRawChunks) controller.enqueue(part);
|