@byte_fluffy/nexra-sdk 0.1.0-alpha.3 → 0.1.0-alpha.31
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 +313 -7
- package/dist/assets-Cusrjj7_.d.ts +61 -0
- package/dist/catalog.d.ts +44 -1
- package/dist/catalog.js +18 -3
- package/dist/chunk-QBW7OCT3.js +756 -0
- package/dist/chunk-QXEDXMFI.js +50848 -0
- package/dist/errors-DZlSwyv8.d.ts +30 -0
- package/dist/index.d.ts +74 -968
- package/dist/index.js +126 -1044
- package/dist/json-Cz0MkWQn.d.ts +11 -0
- package/dist/language.d.ts +38 -0
- package/dist/language.js +631 -0
- package/dist/{catalog-mytCeh5t.d.ts → media-geometry-DAVFGry1.d.ts} +137 -87
- package/dist/media.d.ts +212 -0
- package/dist/media.js +26 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.js +0 -0
- package/dist/voices-2zWJXgCq.d.ts +319 -0
- package/package.json +39 -2
- package/dist/chunk-IMJZBLDE.js +0 -6234
package/dist/language.js
ADDED
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HttpTransport,
|
|
3
|
+
SDK_VERSION,
|
|
4
|
+
createServerSentEventDecoder,
|
|
5
|
+
getLanguageModelBinding,
|
|
6
|
+
jsonBody,
|
|
7
|
+
parseServerSentEvents,
|
|
8
|
+
resolveNexraLanguageReasoning
|
|
9
|
+
} from "./chunk-QXEDXMFI.js";
|
|
10
|
+
import {
|
|
11
|
+
isPublicErrorCode,
|
|
12
|
+
publicErrorMessage,
|
|
13
|
+
resolvePublicErrorLocale
|
|
14
|
+
} from "./chunk-QBW7OCT3.js";
|
|
15
|
+
|
|
16
|
+
// src/language/index.ts
|
|
17
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
18
|
+
import { getModelCapabilities as getAnthropicModelCapabilities } from "@ai-sdk/anthropic/internal";
|
|
19
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
20
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
21
|
+
import { mapReasoningToProviderBudget } from "@ai-sdk/provider-utils";
|
|
22
|
+
|
|
23
|
+
// src/language/chat-model.ts
|
|
24
|
+
import { InvalidResponseDataError } from "@ai-sdk/provider";
|
|
25
|
+
import { StreamingToolCallTracker } from "@ai-sdk/provider-utils";
|
|
26
|
+
|
|
27
|
+
// src/language/chat-prompt.ts
|
|
28
|
+
import { UnsupportedFunctionalityError } from "@ai-sdk/provider";
|
|
29
|
+
import { convertToBase64, resolveFullMediaType } from "@ai-sdk/provider-utils";
|
|
30
|
+
|
|
31
|
+
// src/language/reasoning.ts
|
|
32
|
+
function object(value) {
|
|
33
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
34
|
+
}
|
|
35
|
+
function prepareLanguageReasoning(options, binding) {
|
|
36
|
+
if (!options.reasoning || options.reasoning === "provider-default") return options;
|
|
37
|
+
const fields = object(binding.endpoint.inputSchema?.properties);
|
|
38
|
+
if (!binding.endpoint.capabilities.includes("reasoning")) throw new Error(`Nexra model does not support reasoning: ${binding.spec.id}`);
|
|
39
|
+
const namespace = binding.endpoint.protocol === "openai-responses" ? "openai" : binding.endpoint.protocol === "anthropic-messages" ? "anthropic" : binding.endpoint.protocol === "gemini-generate-content" ? "google" : "nexra";
|
|
40
|
+
const current = object(options.providerOptions?.[namespace]);
|
|
41
|
+
if (current.thinking !== void 0 || current.reasoningEffort !== void 0 || current.reasoning_effort !== void 0 || object(current.thinkingConfig).thinkingLevel !== void 0 || object(current.thinkingConfig).thinkingBudget !== void 0) return options;
|
|
42
|
+
const nativeField = binding.endpoint.protocol === "openai-chat-completions" ? fields.reasoning_effort : binding.endpoint.protocol === "openai-responses" ? object(object(fields.reasoning).properties).effort : binding.endpoint.protocol === "gemini-generate-content" ? object(object(object(object(fields.generationConfig).properties).thinkingConfig).properties).thinkingLevel : void 0;
|
|
43
|
+
const values = object(nativeField).enum;
|
|
44
|
+
if (options.reasoning === "none") {
|
|
45
|
+
const resolved = resolveNexraLanguageReasoning(binding.spec.id, { enabled: false, protocol: binding.endpoint.protocol });
|
|
46
|
+
if (!resolved.canDisable) throw new Error(`Nexra model cannot disable reasoning: ${binding.spec.id}`);
|
|
47
|
+
const desired = object(resolved.providerOptions[namespace]);
|
|
48
|
+
return { ...options, reasoning: void 0, providerOptions: { ...options.providerOptions, [namespace]: { ...desired, ...current } } };
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(values) && !values.includes(options.reasoning)) throw new Error(`Nexra reasoning effort is outside the native contract for ${binding.spec.id}: ${options.reasoning}`);
|
|
51
|
+
if (binding.endpoint.protocol === "openai-chat-completions") {
|
|
52
|
+
const thinkingType = object(object(object(fields.thinking).properties).type).enum;
|
|
53
|
+
return { ...options, providerOptions: { ...options.providerOptions, nexra: {
|
|
54
|
+
...Array.isArray(thinkingType) && thinkingType.includes("enabled") ? { thinking: { type: "enabled" } } : {},
|
|
55
|
+
...current,
|
|
56
|
+
reasoning_effort: options.reasoning
|
|
57
|
+
} } };
|
|
58
|
+
}
|
|
59
|
+
return options;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/language/chat-prompt.ts
|
|
63
|
+
function nativeOptions(part) {
|
|
64
|
+
const value = part.providerOptions?.nexra;
|
|
65
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
66
|
+
}
|
|
67
|
+
function filePart(part, binding) {
|
|
68
|
+
const kind = part.mediaType.split("/")[0];
|
|
69
|
+
if (["image", "audio", "video"].includes(kind) && !binding.spec.capabilities.inputModalities.includes(kind)) {
|
|
70
|
+
throw new UnsupportedFunctionalityError({ functionality: `${kind} input for ${binding.spec.id}` });
|
|
71
|
+
}
|
|
72
|
+
const metadata = nativeOptions(part);
|
|
73
|
+
const reference = part.data.type === "reference" ? part.data.reference.nexra : void 0;
|
|
74
|
+
if (part.data.type === "reference" && typeof reference !== "string") {
|
|
75
|
+
throw new UnsupportedFunctionalityError({ functionality: "file reference without a Nexra reference" });
|
|
76
|
+
}
|
|
77
|
+
const url = part.data.type === "url" ? part.data.url.toString() : void 0;
|
|
78
|
+
const data = part.data.type === "data" ? convertToBase64(part.data.data) : void 0;
|
|
79
|
+
if (kind === "image" || kind === "video") {
|
|
80
|
+
const field = kind === "image" ? "image_url" : "video_url";
|
|
81
|
+
const source = reference ? { file_id: reference } : { url: url ?? `data:${resolveFullMediaType({ part })};base64,${data}` };
|
|
82
|
+
if (!reference && !url && data === void 0) throw new UnsupportedFunctionalityError({ functionality: `${kind} text file input` });
|
|
83
|
+
return { ...metadata, type: field, [field]: { ...asObject(metadata[field]), ...source } };
|
|
84
|
+
}
|
|
85
|
+
if (kind === "audio") {
|
|
86
|
+
if (reference || url) return { ...metadata, type: "input_audio", input_audio: { ...asObject(metadata.input_audio), ...reference ? { file_id: reference } : { url } } };
|
|
87
|
+
const mime = resolveFullMediaType({ part });
|
|
88
|
+
const format = { "audio/mpeg": "mp3", "audio/mp3": "mp3", "audio/wav": "wav", "audio/x-wav": "wav", "audio/aac": "aac", "audio/mp4": "m4a", "audio/x-m4a": "m4a" }[mime];
|
|
89
|
+
if (data === void 0 || !format) throw new UnsupportedFunctionalityError({ functionality: `audio input format ${mime}` });
|
|
90
|
+
return { ...metadata, type: "input_audio", input_audio: { ...asObject(metadata.input_audio), data, format } };
|
|
91
|
+
}
|
|
92
|
+
if (part.mediaType === "application/pdf" || reference) {
|
|
93
|
+
const source = reference ? { file_id: reference } : url ? { file_url: url } : { file_data: `data:application/pdf;base64,${data}`, filename: part.filename ?? "document.pdf" };
|
|
94
|
+
if (!reference && !url && data === void 0) throw new UnsupportedFunctionalityError({ functionality: "PDF text file input" });
|
|
95
|
+
return { ...metadata, type: "file", file: { ...asObject(metadata.file), ...source } };
|
|
96
|
+
}
|
|
97
|
+
if (kind === "text" && part.data.type === "text") return { ...metadata, type: "text", text: part.data.text };
|
|
98
|
+
throw new UnsupportedFunctionalityError({ functionality: `file media type ${part.mediaType}` });
|
|
99
|
+
}
|
|
100
|
+
function asObject(value) {
|
|
101
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
102
|
+
}
|
|
103
|
+
function toolOutput(output, binding) {
|
|
104
|
+
switch (output.type) {
|
|
105
|
+
case "text":
|
|
106
|
+
case "error-text":
|
|
107
|
+
return output.value;
|
|
108
|
+
case "json":
|
|
109
|
+
case "error-json":
|
|
110
|
+
return JSON.stringify(output.value);
|
|
111
|
+
case "execution-denied":
|
|
112
|
+
return output.reason ?? "Tool execution denied";
|
|
113
|
+
case "content":
|
|
114
|
+
return output.value.map((part) => {
|
|
115
|
+
if (part.type === "text") return { ...nativeOptions(part), type: "text", text: part.text };
|
|
116
|
+
if (part.type === "file") return filePart(part, binding);
|
|
117
|
+
throw new UnsupportedFunctionalityError({ functionality: "custom tool output" });
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function messages(prompt, binding) {
|
|
122
|
+
return prompt.flatMap((message) => {
|
|
123
|
+
const metadata = nativeOptions(message);
|
|
124
|
+
if (message.role === "system") return [{ ...metadata, role: "system", content: message.content }];
|
|
125
|
+
if (message.role === "user") {
|
|
126
|
+
const content2 = message.content.map((part) => part.type === "text" ? { ...nativeOptions(part), type: "text", text: part.text } : filePart(part, binding));
|
|
127
|
+
return [{ ...metadata, role: "user", content: content2.length === 1 && content2[0]?.type === "text" ? content2[0].text : content2 }];
|
|
128
|
+
}
|
|
129
|
+
if (message.role === "tool") return message.content.map((part) => {
|
|
130
|
+
if (part.type === "tool-approval-response") throw new UnsupportedFunctionalityError({ functionality: "provider tool approval" });
|
|
131
|
+
return { ...nativeOptions(part), role: "tool", tool_call_id: part.toolCallId, content: toolOutput(part.output, binding) };
|
|
132
|
+
});
|
|
133
|
+
let text = "";
|
|
134
|
+
const content = [];
|
|
135
|
+
let hasFile = false;
|
|
136
|
+
let reasoning = "";
|
|
137
|
+
let encrypted = metadata.encrypted_content;
|
|
138
|
+
const calls = [];
|
|
139
|
+
for (const part of message.content) {
|
|
140
|
+
if (part.type === "text") {
|
|
141
|
+
text += part.text;
|
|
142
|
+
content.push({ ...nativeOptions(part), type: "text", text: part.text });
|
|
143
|
+
} else if (part.type === "file") {
|
|
144
|
+
content.push(filePart(part, binding));
|
|
145
|
+
hasFile = true;
|
|
146
|
+
} else if (part.type === "reasoning") {
|
|
147
|
+
reasoning += part.text;
|
|
148
|
+
encrypted = nativeOptions(part).encrypted_content ?? encrypted;
|
|
149
|
+
} else if (part.type === "tool-call" && !part.providerExecuted) calls.push({ ...nativeOptions(part), id: part.toolCallId, type: "function", function: { name: part.toolName, arguments: JSON.stringify(part.input ?? {}) } });
|
|
150
|
+
else throw new UnsupportedFunctionalityError({ functionality: `assistant ${part.type}` });
|
|
151
|
+
}
|
|
152
|
+
return [{
|
|
153
|
+
...metadata,
|
|
154
|
+
role: "assistant",
|
|
155
|
+
content: hasFile ? content : text,
|
|
156
|
+
...reasoning ? { reasoning_content: reasoning } : {},
|
|
157
|
+
...encrypted ? { encrypted_content: encrypted } : {},
|
|
158
|
+
...calls.length ? { tool_calls: calls } : {}
|
|
159
|
+
}];
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function chatRequest(binding, options, stream) {
|
|
163
|
+
options = prepareLanguageReasoning(options, binding);
|
|
164
|
+
const extension = nativeOptions(options);
|
|
165
|
+
const limits = asObject(asObject(binding.endpoint.inputSchema?.properties).max_tokens);
|
|
166
|
+
if (options.maxOutputTokens !== void 0 && (!Number.isInteger(options.maxOutputTokens) || typeof limits.minimum === "number" && options.maxOutputTokens < limits.minimum || binding.maxOutputTokens !== void 0 && options.maxOutputTokens > binding.maxOutputTokens)) {
|
|
167
|
+
throw new Error(`Nexra output token limit is outside the native contract for ${binding.spec.id}`);
|
|
168
|
+
}
|
|
169
|
+
if (extension.n !== void 0 && extension.n !== 1) throw new UnsupportedFunctionalityError({ functionality: "multiple Chat Completions choices" });
|
|
170
|
+
for (const key of ["model", "messages", "stream"]) {
|
|
171
|
+
if (Object.hasOwn(extension, key)) throw new Error(`Nexra providerOptions cannot override ${key}`);
|
|
172
|
+
}
|
|
173
|
+
if (options.tools?.length && !binding.endpoint.capabilities.includes("tool_call")) {
|
|
174
|
+
throw new UnsupportedFunctionalityError({ functionality: `tools for ${binding.spec.id}` });
|
|
175
|
+
}
|
|
176
|
+
const tools = options.tools?.map((tool) => {
|
|
177
|
+
if (tool.type !== "function") throw new UnsupportedFunctionalityError({ functionality: "provider tools in Chat Completions" });
|
|
178
|
+
return { type: "function", function: { name: tool.name, description: tool.description, parameters: tool.inputSchema, ...tool.strict !== void 0 ? { strict: tool.strict } : {} } };
|
|
179
|
+
});
|
|
180
|
+
const choice = options.toolChoice;
|
|
181
|
+
return {
|
|
182
|
+
...extension.max_completion_tokens === void 0 ? { max_tokens: options.maxOutputTokens } : {},
|
|
183
|
+
temperature: options.temperature,
|
|
184
|
+
top_p: options.topP,
|
|
185
|
+
top_k: options.topK,
|
|
186
|
+
frequency_penalty: options.frequencyPenalty,
|
|
187
|
+
presence_penalty: options.presencePenalty,
|
|
188
|
+
stop: options.stopSequences,
|
|
189
|
+
seed: options.seed,
|
|
190
|
+
...options.reasoning && options.reasoning !== "provider-default" ? { reasoning_effort: options.reasoning } : {},
|
|
191
|
+
...options.responseFormat?.type === "json" ? { response_format: options.responseFormat.schema ? { type: "json_schema", json_schema: { name: options.responseFormat.name ?? "response", description: options.responseFormat.description, schema: options.responseFormat.schema, strict: true } } : { type: "json_object" } } : {},
|
|
192
|
+
...extension,
|
|
193
|
+
model: binding.spec.id,
|
|
194
|
+
messages: messages(options.prompt, binding),
|
|
195
|
+
stream,
|
|
196
|
+
...stream ? { stream_options: { include_usage: true, ...asObject(extension.stream_options) } } : {},
|
|
197
|
+
...tools?.length ? { tools } : {},
|
|
198
|
+
...choice ? { tool_choice: choice.type === "tool" ? { type: "function", function: { name: choice.toolName } } : choice.type } : {}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/language/native-errors.ts
|
|
203
|
+
import { APICallError } from "@ai-sdk/provider";
|
|
204
|
+
function record(value) {
|
|
205
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
206
|
+
}
|
|
207
|
+
var NexraNativeError = class extends APICallError {
|
|
208
|
+
nexraError;
|
|
209
|
+
constructor(context) {
|
|
210
|
+
const headers = new Headers(context.headers);
|
|
211
|
+
const code = headers.get("x-nexra-error-code");
|
|
212
|
+
const locale = resolvePublicErrorLocale(context.acceptLanguage ?? headers.get("content-language"));
|
|
213
|
+
const nexraError = code && isPublicErrorCode(code) ? { code, message: publicErrorMessage(code, locale) } : void 0;
|
|
214
|
+
const body = record(context.body);
|
|
215
|
+
const originalMessage = record(body.error).message ?? body.message;
|
|
216
|
+
const message = nexraError?.message ?? (typeof originalMessage === "string" ? originalMessage : typeof context.body === "string" ? context.body : "The native model request failed.");
|
|
217
|
+
super({
|
|
218
|
+
message,
|
|
219
|
+
url: context.url,
|
|
220
|
+
requestBodyValues: void 0,
|
|
221
|
+
statusCode: context.statusCode,
|
|
222
|
+
responseHeaders: Object.fromEntries(headers),
|
|
223
|
+
responseBody: context.responseBody,
|
|
224
|
+
data: context.body
|
|
225
|
+
});
|
|
226
|
+
this.name = "NexraNativeError";
|
|
227
|
+
this.nexraError = nexraError;
|
|
228
|
+
}
|
|
229
|
+
get code() {
|
|
230
|
+
return this.nexraError?.code;
|
|
231
|
+
}
|
|
232
|
+
get status() {
|
|
233
|
+
return this.statusCode;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
function createNexraNativeFetch(options = {}) {
|
|
237
|
+
const fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
238
|
+
return async (input, init) => {
|
|
239
|
+
const headers = new Headers(options.headers);
|
|
240
|
+
if (input instanceof Request) input.headers.forEach((value, key) => headers.set(key, value));
|
|
241
|
+
new Headers(init?.headers).forEach((value, key) => headers.set(key, value));
|
|
242
|
+
const response = await fetch(input, { ...init, headers });
|
|
243
|
+
const context = {
|
|
244
|
+
url: input instanceof Request ? input.url : String(input),
|
|
245
|
+
statusCode: response.status,
|
|
246
|
+
headers: response.headers,
|
|
247
|
+
acceptLanguage: headers.get("accept-language")
|
|
248
|
+
};
|
|
249
|
+
const eventStream = response.headers.get("content-type")?.includes("text/event-stream");
|
|
250
|
+
if (response.ok && (eventStream || !response.headers.has("x-nexra-error-code"))) return guardNativeErrorFrames(response, context);
|
|
251
|
+
const text = await response.text();
|
|
252
|
+
let body = text;
|
|
253
|
+
try {
|
|
254
|
+
body = JSON.parse(text);
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
throw new NexraNativeError({ ...context, body, responseBody: text });
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function guardNativeErrorFrames(response, context) {
|
|
261
|
+
if (!response.body || !response.headers.get("content-type")?.includes("text/event-stream")) return response;
|
|
262
|
+
const decode = createServerSentEventDecoder();
|
|
263
|
+
const inspect = (events) => {
|
|
264
|
+
for (const event of events) {
|
|
265
|
+
let body;
|
|
266
|
+
try {
|
|
267
|
+
body = JSON.parse(event.data);
|
|
268
|
+
} catch {
|
|
269
|
+
if (event.event === "error") throw new NexraNativeError({ ...context, body: event.data, responseBody: event.data });
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const payload = record(body);
|
|
273
|
+
if (event.event === "error" || payload.type === "error" || payload.type === "response.failed" || payload.error) {
|
|
274
|
+
throw new NexraNativeError({ ...context, body, responseBody: event.data });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
const stream = response.body.pipeThrough(new TransformStream({
|
|
279
|
+
transform(bytes, controller) {
|
|
280
|
+
inspect(decode(bytes));
|
|
281
|
+
controller.enqueue(bytes);
|
|
282
|
+
},
|
|
283
|
+
flush() {
|
|
284
|
+
inspect(decode(void 0, true));
|
|
285
|
+
}
|
|
286
|
+
}));
|
|
287
|
+
return new Response(stream, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/language/chat-model.ts
|
|
291
|
+
function object2(value) {
|
|
292
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
293
|
+
}
|
|
294
|
+
function count(value) {
|
|
295
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
296
|
+
}
|
|
297
|
+
function usage(value) {
|
|
298
|
+
const raw = object2(value);
|
|
299
|
+
const input = count(raw.prompt_tokens);
|
|
300
|
+
const output = count(raw.completion_tokens);
|
|
301
|
+
const cached = count(object2(raw.prompt_tokens_details).cached_tokens);
|
|
302
|
+
const written = count(object2(raw.prompt_tokens_details).cache_write_tokens);
|
|
303
|
+
const reasoning = count(object2(raw.completion_tokens_details).reasoning_tokens);
|
|
304
|
+
return {
|
|
305
|
+
inputTokens: { total: input, noCache: input === void 0 ? void 0 : Math.max(0, input - (cached ?? 0) - (written ?? 0)), cacheRead: cached, cacheWrite: written },
|
|
306
|
+
outputTokens: { total: output, text: output === void 0 ? void 0 : Math.max(0, output - (reasoning ?? 0)), reasoning },
|
|
307
|
+
raw: Object.keys(raw).length ? raw : void 0
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function finishReason(raw) {
|
|
311
|
+
const value = typeof raw === "string" ? raw : void 0;
|
|
312
|
+
const reasons = { stop: "stop", length: "length", tool_calls: "tool-calls", function_call: "tool-calls", content_filter: "content-filter" };
|
|
313
|
+
return { unified: reasons[value] ?? "other", raw: value };
|
|
314
|
+
}
|
|
315
|
+
function invalid(data, message) {
|
|
316
|
+
throw new InvalidResponseDataError({ data, message });
|
|
317
|
+
}
|
|
318
|
+
function choices(payload, response, headers) {
|
|
319
|
+
if (payload.error) throw new NexraNativeError({
|
|
320
|
+
url: response.url,
|
|
321
|
+
statusCode: response.status,
|
|
322
|
+
headers: response.headers,
|
|
323
|
+
body: payload,
|
|
324
|
+
acceptLanguage: headers?.["accept-language"] ?? headers?.["Accept-Language"]
|
|
325
|
+
});
|
|
326
|
+
if (!Array.isArray(payload.choices)) invalid(payload, "Chat Completions response is missing choices");
|
|
327
|
+
if (payload.choices.length > 1) invalid(payload, "Multiple Chat Completions choices are unsupported");
|
|
328
|
+
return payload.choices.map(object2);
|
|
329
|
+
}
|
|
330
|
+
function reasoningMetadata(message) {
|
|
331
|
+
return typeof message.encrypted_content === "string" ? { nexra: { encrypted_content: message.encrypted_content } } : void 0;
|
|
332
|
+
}
|
|
333
|
+
function createChatLanguageModel(binding, transport) {
|
|
334
|
+
return {
|
|
335
|
+
specificationVersion: "v4",
|
|
336
|
+
provider: "nexra.chat",
|
|
337
|
+
modelId: binding.spec.id,
|
|
338
|
+
supportedUrls: Object.fromEntries(binding.spec.capabilities.inputModalities.filter((kind) => ["image", "audio", "video"].includes(kind)).map((kind) => [`${kind}/*`, [/^https?:\/\//]])),
|
|
339
|
+
async doGenerate(options) {
|
|
340
|
+
const body = chatRequest(binding, options, false);
|
|
341
|
+
const response = await transport.response("/chat/completions", { method: "POST", ...jsonBody(body), headers: combineHeaders(options), signal: options.abortSignal }, { retryable: false });
|
|
342
|
+
const payload = object2(await response.json());
|
|
343
|
+
const choice = choices(payload, response, options.headers)[0];
|
|
344
|
+
if (!choice || typeof choice.finish_reason !== "string") invalid(payload, "Chat Completions response is incomplete");
|
|
345
|
+
const message = object2(choice.message);
|
|
346
|
+
const content = [];
|
|
347
|
+
const metadata = reasoningMetadata(message);
|
|
348
|
+
const reasoning = message.reasoning_content ?? message.reasoning;
|
|
349
|
+
if (typeof reasoning === "string" || metadata) content.push({ type: "reasoning", text: typeof reasoning === "string" ? reasoning : "", ...metadata ? { providerMetadata: metadata } : {} });
|
|
350
|
+
if (typeof message.content === "string" && message.content) content.push({ type: "text", text: message.content });
|
|
351
|
+
if (message.content !== null && message.content !== void 0 && typeof message.content !== "string") invalid(message.content, "Chat Completions content must be text");
|
|
352
|
+
if (Array.isArray(message.tool_calls)) {
|
|
353
|
+
for (const raw of message.tool_calls) {
|
|
354
|
+
const call = object2(raw);
|
|
355
|
+
const fn = object2(call.function);
|
|
356
|
+
if (typeof call.id !== "string" || typeof fn.name !== "string" || typeof fn.arguments !== "string") invalid(call, "Invalid Chat Completions tool call");
|
|
357
|
+
content.push({ type: "tool-call", toolCallId: call.id, toolName: fn.name, input: fn.arguments });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
content,
|
|
362
|
+
finishReason: finishReason(choice.finish_reason),
|
|
363
|
+
usage: usage(payload.usage),
|
|
364
|
+
warnings: [],
|
|
365
|
+
request: { body },
|
|
366
|
+
response: {
|
|
367
|
+
id: typeof payload.id === "string" ? payload.id : void 0,
|
|
368
|
+
modelId: typeof payload.model === "string" ? payload.model : void 0,
|
|
369
|
+
headers: Object.fromEntries(response.headers),
|
|
370
|
+
body: payload
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
},
|
|
374
|
+
async doStream(options) {
|
|
375
|
+
if (!binding.supportsStreaming) throw new Error(`Nexra model does not support streaming: ${binding.spec.id}`);
|
|
376
|
+
const body = chatRequest(binding, options, true);
|
|
377
|
+
const abort = new AbortController();
|
|
378
|
+
const signal = options.abortSignal ? AbortSignal.any([options.abortSignal, abort.signal]) : abort.signal;
|
|
379
|
+
const response = await transport.response("/chat/completions", { method: "POST", ...jsonBody(body), headers: combineHeaders(options), signal }, { retryable: false });
|
|
380
|
+
const iterator = parseServerSentEvents(response, { signal });
|
|
381
|
+
let canceled = false;
|
|
382
|
+
const stream = new ReadableStream({
|
|
383
|
+
async start(controller) {
|
|
384
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
385
|
+
const tracker = new StreamingToolCallTracker(controller, { typeValidation: "if-present" });
|
|
386
|
+
const pending = /* @__PURE__ */ new Map();
|
|
387
|
+
const startedTools = /* @__PURE__ */ new Set();
|
|
388
|
+
let textOpen = false;
|
|
389
|
+
let reasoningOpen = false;
|
|
390
|
+
let metadataSent = false;
|
|
391
|
+
let rawUsage;
|
|
392
|
+
let reason;
|
|
393
|
+
let ended = false;
|
|
394
|
+
let encrypted = "";
|
|
395
|
+
try {
|
|
396
|
+
for await (const event of iterator) {
|
|
397
|
+
if (event.data === "[DONE]") {
|
|
398
|
+
ended = true;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
const payload = object2(JSON.parse(event.data));
|
|
402
|
+
if (options.includeRawChunks) controller.enqueue({ type: "raw", rawValue: payload });
|
|
403
|
+
if (!metadataSent) {
|
|
404
|
+
controller.enqueue({ type: "response-metadata", id: typeof payload.id === "string" ? payload.id : void 0, modelId: typeof payload.model === "string" ? payload.model : void 0 });
|
|
405
|
+
metadataSent = true;
|
|
406
|
+
}
|
|
407
|
+
const choice = choices(payload, response, options.headers)[0];
|
|
408
|
+
if (payload.usage) rawUsage = payload.usage;
|
|
409
|
+
if (!choice) continue;
|
|
410
|
+
if (choice.finish_reason != null) reason = choice.finish_reason;
|
|
411
|
+
const delta = object2(choice.delta);
|
|
412
|
+
if (typeof delta.encrypted_content === "string") encrypted += delta.encrypted_content;
|
|
413
|
+
const reasoning = delta.reasoning_content ?? delta.reasoning;
|
|
414
|
+
if (typeof reasoning === "string" && reasoning) {
|
|
415
|
+
if (!reasoningOpen) {
|
|
416
|
+
controller.enqueue({ type: "reasoning-start", id: "reasoning-0" });
|
|
417
|
+
reasoningOpen = true;
|
|
418
|
+
}
|
|
419
|
+
controller.enqueue({ type: "reasoning-delta", id: "reasoning-0", delta: reasoning });
|
|
420
|
+
}
|
|
421
|
+
if (typeof delta.content === "string" && delta.content) {
|
|
422
|
+
if (reasoningOpen) {
|
|
423
|
+
controller.enqueue({ type: "reasoning-end", id: "reasoning-0", ...encrypted ? { providerMetadata: { nexra: { encrypted_content: encrypted } } } : {} });
|
|
424
|
+
reasoningOpen = false;
|
|
425
|
+
}
|
|
426
|
+
if (!textOpen) {
|
|
427
|
+
controller.enqueue({ type: "text-start", id: "text-0" });
|
|
428
|
+
textOpen = true;
|
|
429
|
+
}
|
|
430
|
+
controller.enqueue({ type: "text-delta", id: "text-0", delta: delta.content });
|
|
431
|
+
}
|
|
432
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
433
|
+
for (const item of delta.tool_calls) {
|
|
434
|
+
const part = object2(item);
|
|
435
|
+
const index = part.index ?? 0;
|
|
436
|
+
if (startedTools.has(index)) {
|
|
437
|
+
tracker.processDelta(part);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const saved = pending.get(index);
|
|
441
|
+
const merged = { ...saved, ...part, function: {
|
|
442
|
+
...saved?.function,
|
|
443
|
+
...part.function,
|
|
444
|
+
arguments: `${saved?.function?.arguments ?? ""}${part.function?.arguments ?? ""}`
|
|
445
|
+
} };
|
|
446
|
+
if (merged.id && merged.function?.name) {
|
|
447
|
+
pending.delete(index);
|
|
448
|
+
startedTools.add(index);
|
|
449
|
+
tracker.processDelta(merged);
|
|
450
|
+
} else pending.set(index, merged);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (!ended || typeof reason !== "string") invalid({ ended, finish_reason: reason }, "Chat Completions stream ended before its terminal response");
|
|
455
|
+
if (pending.size) invalid([...pending.values()], "Chat Completions stream contains incomplete tool calls");
|
|
456
|
+
if (encrypted && !reasoningOpen) {
|
|
457
|
+
controller.enqueue({ type: "reasoning-start", id: "encrypted-0" });
|
|
458
|
+
controller.enqueue({ type: "reasoning-end", id: "encrypted-0", providerMetadata: { nexra: { encrypted_content: encrypted } } });
|
|
459
|
+
}
|
|
460
|
+
if (reasoningOpen) controller.enqueue({ type: "reasoning-end", id: "reasoning-0", ...encrypted ? { providerMetadata: { nexra: { encrypted_content: encrypted } } } : {} });
|
|
461
|
+
if (textOpen) controller.enqueue({ type: "text-end", id: "text-0" });
|
|
462
|
+
tracker.flush();
|
|
463
|
+
controller.enqueue({ type: "finish", finishReason: finishReason(reason), usage: usage(rawUsage) });
|
|
464
|
+
} catch (error) {
|
|
465
|
+
if (!canceled) controller.enqueue({ type: "error", error });
|
|
466
|
+
} finally {
|
|
467
|
+
if (!canceled) controller.close();
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
async cancel(reason) {
|
|
471
|
+
canceled = true;
|
|
472
|
+
abort.abort(reason);
|
|
473
|
+
await iterator.return(void 0);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
return { stream, request: { body }, response: { headers: Object.fromEntries(response.headers) } };
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function combineHeaders(options) {
|
|
481
|
+
const headers = new Headers(options.headers);
|
|
482
|
+
headers.set("content-type", "application/json");
|
|
483
|
+
return headers;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/language/index.ts
|
|
487
|
+
function createNexraLanguageModel(modelId, options) {
|
|
488
|
+
const binding = getLanguageModelBinding(modelId, options);
|
|
489
|
+
const apiKey = options.apiKey.trim();
|
|
490
|
+
if (!apiKey) throw new Error("Nexra language model requires an API key");
|
|
491
|
+
const fetch = createNexraNativeFetch(options);
|
|
492
|
+
const transport = new HttpTransport({ ...options, baseUrl: options.baseUrl?.trim().replace(/\/+$/, ""), apiKey, maxRetries: 0 });
|
|
493
|
+
const root = transport.baseUrl.replace(/\/(?:v1|v1beta)(?:\/models)?$/, "");
|
|
494
|
+
if (binding.endpoint.protocol === "openai-chat-completions") return createChatLanguageModel(binding, new HttpTransport({ ...options, fetch, baseUrl: `${root}/v1`, apiKey, maxRetries: 0 }));
|
|
495
|
+
const headers = new Headers(options.headers);
|
|
496
|
+
if (!headers.has("authorization")) headers.set("authorization", `Bearer ${apiKey}`);
|
|
497
|
+
headers.set("x-nexra-sdk", `typescript/${SDK_VERSION}`);
|
|
498
|
+
const common = { apiKey, headers: Object.fromEntries(headers), fetch };
|
|
499
|
+
let model;
|
|
500
|
+
if (binding.endpoint.protocol === "gemini-generate-content") {
|
|
501
|
+
model = createGoogleGenerativeAI({ ...common, name: "nexra.google", baseURL: `${root}/v1beta/models` })(binding.spec.id);
|
|
502
|
+
} else if (binding.endpoint.protocol === "openai-responses") {
|
|
503
|
+
model = createOpenAI({ ...common, name: "nexra.openai", baseURL: `${root}/v1` }).responses(binding.spec.id);
|
|
504
|
+
} else {
|
|
505
|
+
model = createAnthropic({ ...common, name: "nexra.anthropic", baseURL: `${root}/v1` })(binding.spec.id);
|
|
506
|
+
}
|
|
507
|
+
return withNativeUsage(model, binding);
|
|
508
|
+
}
|
|
509
|
+
function object3(value) {
|
|
510
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
511
|
+
}
|
|
512
|
+
function prepare(options, binding, stream) {
|
|
513
|
+
options = prepareLanguageReasoning(options, binding);
|
|
514
|
+
const properties = object3(binding.endpoint.inputSchema?.properties);
|
|
515
|
+
const tokenField = object3(binding.endpoint.protocol === "gemini-generate-content" ? object3(object3(properties.generationConfig).properties).maxOutputTokens : properties.max_output_tokens ?? properties.max_tokens);
|
|
516
|
+
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}`);
|
|
517
|
+
if (options.tools?.length && !binding.endpoint.capabilities.includes("tool_call")) throw new Error(`Nexra model does not support tools: ${binding.spec.id}`);
|
|
518
|
+
const providerOptions = { ...options.providerOptions };
|
|
519
|
+
if (binding.endpoint.protocol === "openai-responses") {
|
|
520
|
+
const current = object3(providerOptions.openai);
|
|
521
|
+
providerOptions.openai = {
|
|
522
|
+
store: false,
|
|
523
|
+
forceReasoning: binding.endpoint.capabilities.includes("reasoning"),
|
|
524
|
+
...current
|
|
525
|
+
};
|
|
526
|
+
const reasoning = object3(providerOptions.openai);
|
|
527
|
+
if (reasoning.forceReasoning === true && (options.temperature !== void 0 || options.topP !== void 0)) {
|
|
528
|
+
throw new Error(`Nexra Responses adapter cannot preserve explicit sampling parameters with reasoning for ${binding.spec.id}`);
|
|
529
|
+
}
|
|
530
|
+
if (reasoning.forceReasoning === false && (reasoning.reasoningEffort !== void 0 || reasoning.reasoningSummary !== void 0 || reasoning.reasoningMode !== void 0 || reasoning.reasoningContext !== void 0 || options.reasoning !== void 0 && options.reasoning !== "provider-default")) {
|
|
531
|
+
throw new Error(`Nexra Responses adapter cannot preserve reasoning options with forceReasoning disabled for ${binding.spec.id}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (binding.endpoint.protocol === "anthropic-messages") {
|
|
535
|
+
const capabilities = getAnthropicModelCapabilities(binding.spec.id);
|
|
536
|
+
const native = { ...object3(providerOptions.anthropic), ...object3(providerOptions.nexra) };
|
|
537
|
+
let thinking = object3(native.thinking);
|
|
538
|
+
if (!native.thinking && native.effort == null && options.reasoning && options.reasoning !== "provider-default") {
|
|
539
|
+
thinking = options.reasoning === "none" ? { type: "disabled" } : capabilities.supportsAdaptiveThinking ? { type: "adaptive" } : { type: "enabled", budgetTokens: mapReasoningToProviderBudget({
|
|
540
|
+
reasoning: options.reasoning,
|
|
541
|
+
maxOutputTokens: capabilities.maxOutputTokens,
|
|
542
|
+
maxReasoningBudget: capabilities.maxOutputTokens,
|
|
543
|
+
warnings: []
|
|
544
|
+
}) };
|
|
545
|
+
}
|
|
546
|
+
const budget = thinking.type === "enabled" ? thinking.budgetTokens ?? 1024 : 0;
|
|
547
|
+
if (typeof budget !== "number" || !Number.isInteger(budget) || budget < 0) {
|
|
548
|
+
throw new Error(`Nexra thinking token budget is invalid for ${binding.spec.id}`);
|
|
549
|
+
}
|
|
550
|
+
const maximum = Math.min(binding.maxOutputTokens ?? Infinity, capabilities.isKnownModel ? capabilities.maxOutputTokens : Infinity);
|
|
551
|
+
const visible = options.maxOutputTokens ?? Math.min(capabilities.maxOutputTokens, maximum - budget);
|
|
552
|
+
if (visible < 1 || visible + budget > maximum) {
|
|
553
|
+
throw new Error(`Nexra output token limit including thinking is outside the native contract for ${binding.spec.id}`);
|
|
554
|
+
}
|
|
555
|
+
options = { ...options, maxOutputTokens: visible };
|
|
556
|
+
}
|
|
557
|
+
if (stream && binding.endpoint.protocol === "gemini-generate-content" && binding.endpoint.capabilities.includes("reasoning")) {
|
|
558
|
+
const google = object3(providerOptions.google);
|
|
559
|
+
providerOptions.google = { ...google, thinkingConfig: { includeThoughts: true, ...object3(google.thinkingConfig) } };
|
|
560
|
+
}
|
|
561
|
+
return { ...options, providerOptions };
|
|
562
|
+
}
|
|
563
|
+
function nativeUsage(protocol, value) {
|
|
564
|
+
const payload = object3(value);
|
|
565
|
+
const raw = protocol === "gemini-generate-content" ? payload.usageMetadata : protocol === "anthropic-messages" ? object3(payload.message).usage ?? payload.usage : object3(payload.response).usage ?? payload.usage;
|
|
566
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0;
|
|
567
|
+
}
|
|
568
|
+
function mergeRecords(previous, next) {
|
|
569
|
+
const merged = { ...previous, ...next };
|
|
570
|
+
for (const [key, value] of Object.entries(next)) {
|
|
571
|
+
if (value && typeof value === "object" && !Array.isArray(value)) merged[key] = { ...object3(previous?.[key]), ...object3(value) };
|
|
572
|
+
}
|
|
573
|
+
return merged;
|
|
574
|
+
}
|
|
575
|
+
function withNativeUsage(model, binding) {
|
|
576
|
+
return {
|
|
577
|
+
specificationVersion: "v4",
|
|
578
|
+
provider: model.provider,
|
|
579
|
+
modelId: model.modelId,
|
|
580
|
+
supportedUrls: model.supportedUrls,
|
|
581
|
+
async doGenerate(options) {
|
|
582
|
+
const result = await model.doGenerate(prepare(options, binding, false));
|
|
583
|
+
const raw = nativeUsage(binding.endpoint.protocol, result.response?.body);
|
|
584
|
+
return raw ? { ...result, usage: { ...result.usage, raw } } : result;
|
|
585
|
+
},
|
|
586
|
+
async doStream(options) {
|
|
587
|
+
if (!binding.supportsStreaming) throw new Error(`Nexra model does not support streaming: ${binding.spec.id}`);
|
|
588
|
+
const result = await model.doStream({ ...prepare(options, binding, true), includeRawChunks: true });
|
|
589
|
+
let raw;
|
|
590
|
+
let terminal = false;
|
|
591
|
+
let failed = false;
|
|
592
|
+
let finish;
|
|
593
|
+
const textMetadata = /* @__PURE__ */ new Map();
|
|
594
|
+
const reasoningMetadata2 = /* @__PURE__ */ new Map();
|
|
595
|
+
return { ...result, stream: result.stream.pipeThrough(new TransformStream({
|
|
596
|
+
transform(part, controller) {
|
|
597
|
+
if (part.type === "raw") {
|
|
598
|
+
const payload = object3(part.rawValue);
|
|
599
|
+
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");
|
|
600
|
+
const next = nativeUsage(binding.endpoint.protocol, part.rawValue);
|
|
601
|
+
if (next) raw = mergeRecords(raw, next);
|
|
602
|
+
if (options.includeRawChunks) controller.enqueue(part);
|
|
603
|
+
} else if (part.type === "finish") finish = part;
|
|
604
|
+
else {
|
|
605
|
+
if (part.type === "error") failed = true;
|
|
606
|
+
if (part.type === "text-start" || part.type === "text-delta" || part.type === "text-end" || part.type === "reasoning-start" || part.type === "reasoning-delta" || part.type === "reasoning-end") {
|
|
607
|
+
const metadata = part.type.startsWith("text-") ? textMetadata : reasoningMetadata2;
|
|
608
|
+
if (part.providerMetadata) metadata.set(part.id, mergeRecords(metadata.get(part.id), part.providerMetadata));
|
|
609
|
+
if (part.type === "text-end" || part.type === "reasoning-end") {
|
|
610
|
+
const retained = metadata.get(part.id);
|
|
611
|
+
metadata.delete(part.id);
|
|
612
|
+
if (retained) part = { ...part, providerMetadata: retained };
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
controller.enqueue(part);
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
flush(controller) {
|
|
619
|
+
if (failed) return;
|
|
620
|
+
if (!terminal || !finish) controller.enqueue({ type: "error", error: new Error(`Nexra ${binding.endpoint.protocol} stream ended before its terminal response`) });
|
|
621
|
+
else controller.enqueue(raw ? { ...finish, usage: { ...finish.usage, raw } } : finish);
|
|
622
|
+
}
|
|
623
|
+
})) };
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
export {
|
|
628
|
+
NexraNativeError,
|
|
629
|
+
createNexraLanguageModel,
|
|
630
|
+
createNexraNativeFetch
|
|
631
|
+
};
|