@byte_fluffy/nexra-sdk 0.1.0-alpha.46.1 → 0.1.0-alpha.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  projectMediaGeometry,
21
21
  queryString,
22
22
  sleep
23
- } from "./chunk-JK6LUGM5.js";
23
+ } from "./chunk-2IWCEEDI.js";
24
24
  import {
25
25
  MEDIA_KINDS,
26
26
  TASK_STATUSES,
package/dist/language.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  jsonBody,
7
7
  parseServerSentEvents,
8
8
  resolveNexraLanguageReasoning
9
- } from "./chunk-JK6LUGM5.js";
9
+ } from "./chunk-2IWCEEDI.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: Object.fromEntries(binding.spec.capabilities.inputModalities.filter((kind) => ["image", "audio", "video"].includes(kind)).map((kind) => [`${kind}/*`, [/^https?:\/\//]])),
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,190 @@ 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
+ const projected = 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
+ if (projected.type === "output_text" && projected.annotations === void 0) projected.annotations = [];
575
+ return projected;
576
+ }
577
+ function citations(value) {
578
+ if (Array.isArray(value)) return value.flatMap(citations);
579
+ const node = object3(value);
580
+ if (isUnpositionedCitation(node)) return [node];
581
+ return Object.values(node).flatMap((item) => item && typeof item === "object" ? citations(item) : []);
582
+ }
583
+ function nativeSearchBody(value, call) {
584
+ const extension = call.providerOptions?.nexra?.web_search;
585
+ if (extension === void 0) return value;
586
+ if (!extension || typeof extension !== "object" || Array.isArray(extension)) throw new Error("Nexra web_search options must be a native tool object");
587
+ const body = object3(value);
588
+ if (!Array.isArray(body.tools) || !body.tools.some((tool) => object3(tool).type === "web_search")) {
589
+ throw new Error("Nexra web_search options require an enabled web_search tool");
590
+ }
591
+ return { ...body, tools: body.tools.map((tool) => object3(tool).type === "web_search" ? { ...object3(tool), ...extension, type: "web_search" } : tool) };
592
+ }
593
+ function nativeSearchRequest(init, call) {
594
+ return typeof init?.body === "string" ? { ...init, body: JSON.stringify(nativeSearchBody(JSON.parse(init.body), call)) } : init;
595
+ }
596
+ function createResponsesLanguageModel(options) {
597
+ const factory = (fetch) => createOpenAI({
598
+ apiKey: options.apiKey,
599
+ headers: options.headers,
600
+ name: "nexra.openai",
601
+ baseURL: options.baseUrl,
602
+ fetch
603
+ }).responses(options.modelId);
604
+ const base = factory(options.fetch);
605
+ return {
606
+ specificationVersion: "v4",
607
+ provider: base.provider,
608
+ modelId: base.modelId,
609
+ supportedUrls: base.supportedUrls,
610
+ async doGenerate(call) {
611
+ let original;
612
+ const fetch = async (input, init) => {
613
+ const response = await options.fetch(input, nativeSearchRequest(init, call));
614
+ if (!response.ok || !response.headers.get("content-type")?.includes("application/json")) return response;
615
+ const text = await response.text();
616
+ try {
617
+ original = JSON.parse(text);
618
+ } catch {
619
+ return new Response(text, response);
620
+ }
621
+ return new Response(JSON.stringify(project(original)), response);
622
+ };
623
+ const result = await factory(fetch).doGenerate(call);
624
+ const seen = /* @__PURE__ */ new Set();
625
+ const sources = sourceParts(original, seen);
626
+ return {
627
+ ...result,
628
+ content: [...result.content, ...sources],
629
+ request: result.request ? { ...result.request, body: nativeSearchBody(result.request.body, call) } : void 0,
630
+ response: { ...result.response, ...original === void 0 ? {} : { body: original } }
631
+ };
632
+ },
633
+ async doStream(call) {
634
+ const entries = /* @__PURE__ */ new Map();
635
+ let before = [];
636
+ const canceled = new AbortController();
637
+ const fetch = async (input, init) => {
638
+ const response = await options.fetch(input, nativeSearchRequest(init, call));
639
+ if (!response.ok || !response.headers.get("content-type")?.includes("text/event-stream")) return response;
640
+ const signal = init?.signal ? AbortSignal.any([init.signal, canceled.signal]) : canceled.signal;
641
+ const iterator = parseServerSentEvents(response, { signal });
642
+ const encoder = new TextEncoder();
643
+ const body = new ReadableStream({
644
+ async pull(controller) {
645
+ try {
646
+ while (true) {
647
+ const next = await iterator.next();
648
+ if (next.done) {
649
+ controller.close();
650
+ return;
651
+ }
652
+ const frame = next.value;
653
+ let native;
654
+ try {
655
+ native = JSON.parse(frame.data);
656
+ } catch {
657
+ controller.enqueue(encoder.encode(`${frame.event ? `event: ${frame.event}
658
+ ` : ""}data: ${frame.data}
659
+
660
+ `));
661
+ return;
662
+ }
663
+ if (object3(native).type === "response.output_text.annotation.added" && isUnpositionedCitation(object3(native).annotation)) {
664
+ before.push(native);
665
+ continue;
666
+ }
667
+ const projected = project(native);
668
+ const key = JSON.stringify(projected);
669
+ const queue = entries.get(key) ?? [];
670
+ queue.push({ original: native, before });
671
+ before = [];
672
+ entries.set(key, queue);
673
+ controller.enqueue(encoder.encode(`${frame.id ? `id: ${frame.id}
674
+ ` : ""}${frame.event ? `event: ${frame.event}
675
+ ` : ""}${frame.retry !== void 0 ? `retry: ${frame.retry}
676
+ ` : ""}data: ${key}
677
+
678
+ `));
679
+ return;
680
+ }
681
+ } catch (error) {
682
+ controller.error(error);
683
+ await iterator.return(void 0);
684
+ }
685
+ },
686
+ async cancel(reason) {
687
+ canceled.abort(reason);
688
+ await iterator.return(void 0);
689
+ }
690
+ });
691
+ return new Response(body, response);
692
+ };
693
+ const result = await factory(fetch).doStream({ ...call, includeRawChunks: true });
694
+ const seen = /* @__PURE__ */ new Set();
695
+ return {
696
+ ...result,
697
+ request: result.request ? { ...result.request, body: nativeSearchBody(result.request.body, call) } : void 0,
698
+ stream: result.stream.pipeThrough(new TransformStream({
699
+ transform(part, controller) {
700
+ if (part.type !== "raw") {
701
+ controller.enqueue(part);
702
+ return;
703
+ }
704
+ const key = JSON.stringify(part.rawValue);
705
+ const queue = entries.get(key);
706
+ const entry = queue?.shift();
707
+ if (queue?.length === 0) entries.delete(key);
708
+ const native = entry?.original ?? part.rawValue;
709
+ for (const frame of [...entry?.before ?? [], native]) {
710
+ for (const source of sourceParts(frame, seen)) controller.enqueue(source);
711
+ if (call.includeRawChunks) controller.enqueue({ type: "raw", rawValue: frame });
712
+ }
713
+ },
714
+ flush(controller) {
715
+ for (const frame of before) {
716
+ for (const source of sourceParts(frame, seen)) controller.enqueue(source);
717
+ if (call.includeRawChunks) controller.enqueue({ type: "raw", rawValue: frame });
718
+ }
719
+ entries.clear();
720
+ before = [];
721
+ }
722
+ }))
723
+ };
724
+ }
725
+ };
726
+ }
727
+ function sourceParts(value, seen) {
728
+ return citations(value).flatMap((annotation) => {
729
+ const key = JSON.stringify([annotation.url, annotation.title]);
730
+ if (seen.has(key)) return [];
731
+ seen.add(key);
732
+ return [{
733
+ type: "source",
734
+ sourceType: "url",
735
+ id: `native-source-${seen.size}`,
736
+ url: annotation.url,
737
+ ...typeof annotation.title === "string" ? { title: annotation.title } : {}
738
+ }];
739
+ });
740
+ }
741
+
531
742
  // src/language/index.ts
532
743
  function createNexraLanguageModel(modelId, options) {
533
744
  const binding = getLanguageModelBinding(modelId, options);
@@ -553,30 +764,30 @@ function createNexraLanguageModel(modelId, options) {
553
764
  input,
554
765
  typeof init?.body === "string" ? { ...init, body: JSON.stringify(explicitResponsesMessages(JSON.parse(init.body))) } : init
555
766
  );
556
- model = createOpenAI({ ...common, name: "nexra.openai", baseURL: `${root}/v1`, fetch: responsesFetch }).responses(binding.spec.id);
767
+ model = createResponsesLanguageModel({ modelId: binding.spec.id, apiKey, headers: common.headers, baseUrl: `${root}/v1`, fetch: responsesFetch });
557
768
  } else {
558
769
  model = createAnthropic({ ...common, name: "nexra.anthropic", baseURL: `${root}/v1` })(binding.spec.id);
559
770
  }
560
771
  return withNativeContract(model, binding);
561
772
  }
562
- function object3(value) {
773
+ function object4(value) {
563
774
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
564
775
  }
565
776
  function explicitResponsesMessages(value) {
566
- const body = object3(value);
777
+ const body = object4(value);
567
778
  if (!Array.isArray(body.input)) return value;
568
779
  return { ...body, input: body.input.map((value2) => {
569
- const item = object3(value2);
780
+ const item = object4(value2);
570
781
  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
782
  }) };
572
783
  }
573
784
  function googleNativeBody(binding, value) {
574
- const body = object3(value);
575
- const config = object3(body.generationConfig);
576
- const thinking = object3(config.thinkingConfig);
577
- const fields = object3(binding.endpoint.inputSchema?.properties);
578
- const nativeThinking = object3(object3(object3(object3(fields.generationConfig).properties).thinkingConfig).properties);
579
- const values = object3(nativeThinking.thinkingLevel).enum;
785
+ const body = object4(value);
786
+ const config = object4(body.generationConfig);
787
+ const thinking = object4(config.thinkingConfig);
788
+ const fields = object4(binding.endpoint.inputSchema?.properties);
789
+ const nativeThinking = object4(object4(object4(object4(fields.generationConfig).properties).thinkingConfig).properties);
790
+ const values = object4(nativeThinking.thinkingLevel).enum;
580
791
  const level = typeof thinking.thinkingLevel === "string" && Array.isArray(values) ? values.find((value2) => typeof value2 === "string" && value2.toLowerCase() === String(thinking.thinkingLevel).toLowerCase()) : void 0;
581
792
  return level === void 0 || level === thinking.thinkingLevel ? value : { ...body, generationConfig: { ...config, thinkingConfig: { ...thinking, thinkingLevel: level } } };
582
793
  }
@@ -584,25 +795,29 @@ function nativeRequest(binding, request, stream = false) {
584
795
  if (!request) return request;
585
796
  return binding.endpoint.protocol === "openai-responses" ? {
586
797
  ...request,
587
- body: explicitResponsesMessages(stream ? { ...object3(request.body), stream: true } : request.body)
798
+ body: explicitResponsesMessages(stream ? { ...object4(request.body), stream: true } : request.body)
588
799
  } : binding.endpoint.protocol === "gemini-generate-content" ? { ...request, body: googleNativeBody(binding, request.body) } : request;
589
800
  }
590
801
  function prepare(options, binding, stream) {
591
802
  binding = getLanguageModelBinding(binding.spec.id, { protocol: binding.endpoint.protocol, stream });
592
803
  options = prepareLanguageReasoning(options, binding);
593
- const properties = object3(binding.endpoint.inputSchema?.properties);
594
- const tokenField = object3(binding.endpoint.protocol === "gemini-generate-content" ? object3(object3(properties.generationConfig).properties).maxOutputTokens : properties.max_output_tokens ?? properties.max_tokens);
804
+ const properties = object4(binding.endpoint.inputSchema?.properties);
805
+ const choices2 = object4(properties.tool_choice).enum;
806
+ if (options.toolChoice && Array.isArray(choices2) && !choices2.includes(options.toolChoice.type)) {
807
+ throw new Error(`Nexra tool choice is outside the native contract for ${binding.spec.id}: ${options.toolChoice.type}`);
808
+ }
809
+ const tokenField = object4(binding.endpoint.protocol === "gemini-generate-content" ? object4(object4(properties.generationConfig).properties).maxOutputTokens : properties.max_output_tokens ?? properties.max_tokens);
595
810
  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
811
  if (options.tools?.length && !binding.endpoint.capabilities.includes("tool_call")) throw new Error(`Nexra model does not support tools: ${binding.spec.id}`);
597
812
  const providerOptions = { ...options.providerOptions };
598
813
  if (binding.endpoint.protocol === "openai-responses") {
599
- const current = object3(providerOptions.openai);
814
+ const current = object4(providerOptions.openai);
600
815
  providerOptions.openai = {
601
816
  store: false,
602
817
  forceReasoning: binding.endpoint.capabilities.includes("reasoning"),
603
818
  ...current
604
819
  };
605
- const reasoning = object3(providerOptions.openai);
820
+ const reasoning = object4(providerOptions.openai);
606
821
  if (reasoning.forceReasoning === true && (options.temperature !== void 0 || options.topP !== void 0)) {
607
822
  throw new Error(`Nexra Responses adapter cannot preserve explicit sampling parameters with reasoning for ${binding.spec.id}`);
608
823
  }
@@ -612,8 +827,8 @@ function prepare(options, binding, stream) {
612
827
  }
613
828
  if (binding.endpoint.protocol === "anthropic-messages") {
614
829
  const capabilities = getAnthropicModelCapabilities(binding.spec.id);
615
- const native = { ...object3(providerOptions.anthropic), ...object3(providerOptions.nexra) };
616
- let thinking = object3(native.thinking);
830
+ const native = { ...object4(providerOptions.anthropic), ...object4(providerOptions.nexra) };
831
+ let thinking = object4(native.thinking);
617
832
  if (!native.thinking && native.effort == null && options.reasoning && options.reasoning !== "provider-default") {
618
833
  thinking = options.reasoning === "none" ? { type: "disabled" } : capabilities.supportsAdaptiveThinking ? { type: "adaptive" } : { type: "enabled", budgetTokens: mapReasoningToProviderBudget({
619
834
  reasoning: options.reasoning,
@@ -634,20 +849,20 @@ function prepare(options, binding, stream) {
634
849
  options = { ...options, maxOutputTokens: visible };
635
850
  }
636
851
  if (stream && binding.endpoint.protocol === "gemini-generate-content" && binding.endpoint.capabilities.includes("reasoning")) {
637
- const google = object3(providerOptions.google);
638
- providerOptions.google = { ...google, thinkingConfig: { includeThoughts: true, ...object3(google.thinkingConfig) } };
852
+ const google = object4(providerOptions.google);
853
+ providerOptions.google = { ...google, thinkingConfig: { includeThoughts: true, ...object4(google.thinkingConfig) } };
639
854
  }
640
855
  return { ...options, providerOptions };
641
856
  }
642
857
  function nativeUsage(protocol, value) {
643
- const payload = object3(value);
644
- const raw = protocol === "gemini-generate-content" ? payload.usageMetadata : protocol === "anthropic-messages" ? object3(payload.message).usage ?? payload.usage : object3(payload.response).usage ?? payload.usage;
858
+ const payload = object4(value);
859
+ const raw = protocol === "gemini-generate-content" ? payload.usageMetadata : protocol === "anthropic-messages" ? object4(payload.message).usage ?? payload.usage : object4(payload.response).usage ?? payload.usage;
645
860
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0;
646
861
  }
647
862
  function mergeRecords(previous, next) {
648
863
  const merged = { ...previous, ...next };
649
864
  for (const [key, value] of Object.entries(next)) {
650
- if (value && typeof value === "object" && !Array.isArray(value)) merged[key] = { ...object3(previous?.[key]), ...object3(value) };
865
+ if (value && typeof value === "object" && !Array.isArray(value)) merged[key] = { ...object4(previous?.[key]), ...object4(value) };
651
866
  }
652
867
  return merged;
653
868
  }
@@ -681,8 +896,8 @@ function withNativeContract(model, binding) {
681
896
  stream: result.stream.pipeThrough(new TransformStream({
682
897
  transform(part, controller) {
683
898
  if (part.type === "raw") {
684
- const payload = object3(part.rawValue);
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 object3(candidate).finishReason === "string");
899
+ const payload = object4(part.rawValue);
900
+ 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
901
  const next = nativeUsage(binding.endpoint.protocol, part.rawValue);
687
902
  if (next) raw = mergeRecords(raw, next);
688
903
  if (options.includeRawChunks) controller.enqueue(part);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byte_fluffy/nexra-sdk",
3
- "version": "0.1.0-alpha.46.1",
3
+ "version": "0.1.0-alpha.48",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "sideEffects": false,
@@ -72,10 +72,20 @@
72
72
  "@ai-sdk/provider-utils": "^5.0.21"
73
73
  },
74
74
  "peerDependenciesMeta": {
75
- "@ai-sdk/anthropic": { "optional": true },
76
- "@ai-sdk/google": { "optional": true },
77
- "@ai-sdk/openai": { "optional": true },
78
- "@ai-sdk/provider": { "optional": true },
79
- "@ai-sdk/provider-utils": { "optional": true }
75
+ "@ai-sdk/anthropic": {
76
+ "optional": true
77
+ },
78
+ "@ai-sdk/google": {
79
+ "optional": true
80
+ },
81
+ "@ai-sdk/openai": {
82
+ "optional": true
83
+ },
84
+ "@ai-sdk/provider": {
85
+ "optional": true
86
+ },
87
+ "@ai-sdk/provider-utils": {
88
+ "optional": true
89
+ }
80
90
  }
81
91
  }