@k2b/nessi 0.10.0-rc.0

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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/aggregates.d.ts +7 -0
  4. package/aggregates.js +115 -0
  5. package/ai/complete-from-stream.d.ts +2 -0
  6. package/ai/complete-from-stream.js +36 -0
  7. package/ai/index.d.ts +10 -0
  8. package/ai/index.js +9 -0
  9. package/ai/providers/anthropic.d.ts +13 -0
  10. package/ai/providers/anthropic.js +266 -0
  11. package/ai/providers/gemini.d.ts +12 -0
  12. package/ai/providers/gemini.js +192 -0
  13. package/ai/providers/mistral.d.ts +12 -0
  14. package/ai/providers/mistral.js +287 -0
  15. package/ai/providers/ollama.d.ts +10 -0
  16. package/ai/providers/ollama.js +241 -0
  17. package/ai/providers/openai-compatible.d.ts +2 -0
  18. package/ai/providers/openai-compatible.js +349 -0
  19. package/ai/providers/openai.d.ts +12 -0
  20. package/ai/providers/openai.js +22 -0
  21. package/ai/providers/openrouter.d.ts +13 -0
  22. package/ai/providers/openrouter.js +28 -0
  23. package/ai/providers/vllm.d.ts +11 -0
  24. package/ai/providers/vllm.js +22 -0
  25. package/ai/shared/errors.d.ts +15 -0
  26. package/ai/shared/errors.js +56 -0
  27. package/ai/shared/json.d.ts +3 -0
  28. package/ai/shared/json.js +15 -0
  29. package/ai/shared/messages.d.ts +15 -0
  30. package/ai/shared/messages.js +58 -0
  31. package/ai/shared/ndjson.d.ts +4 -0
  32. package/ai/shared/ndjson.js +60 -0
  33. package/ai/shared/sse.d.ts +15 -0
  34. package/ai/shared/sse.js +79 -0
  35. package/ai/shared/stream-helpers.d.ts +13 -0
  36. package/ai/shared/stream-helpers.js +105 -0
  37. package/ai/shared/tool-call-ids.d.ts +5 -0
  38. package/ai/shared/tool-call-ids.js +38 -0
  39. package/ai/shared/tool-stream-normalizer.d.ts +6 -0
  40. package/ai/shared/tool-stream-normalizer.js +271 -0
  41. package/ai/shared/tools.d.ts +29 -0
  42. package/ai/shared/tools.js +25 -0
  43. package/ai/shared/usage.d.ts +3 -0
  44. package/ai/shared/usage.js +5 -0
  45. package/ai/types.d.ts +252 -0
  46. package/ai/types.js +0 -0
  47. package/compact.d.ts +5 -0
  48. package/compact.js +108 -0
  49. package/index.d.ts +11 -0
  50. package/index.js +12 -0
  51. package/nessi.d.ts +2 -0
  52. package/nessi.js +1250 -0
  53. package/package.json +80 -0
  54. package/providers/ollama.d.ts +2 -0
  55. package/providers/ollama.js +1 -0
  56. package/providers/openai.d.ts +2 -0
  57. package/providers/openai.js +1 -0
  58. package/providers/openrouter.d.ts +2 -0
  59. package/providers/openrouter.js +1 -0
  60. package/stores.d.ts +11 -0
  61. package/stores.js +42 -0
  62. package/structured.d.ts +9 -0
  63. package/structured.js +413 -0
  64. package/tools.d.ts +25 -0
  65. package/tools.js +36 -0
  66. package/types.d.ts +290 -0
  67. package/types.js +3 -0
  68. package/utils.d.ts +15 -0
  69. package/utils.js +47 -0
@@ -0,0 +1,287 @@
1
+ import { formatConnectionError, normalizeHttpError } from "../shared/errors.js";
2
+ import { assertOnlySupportedFiles, buildAssistantMessage } from "../shared/messages.js";
3
+ import { ensureRecord, safeJsonParse, stringifyJson } from "../shared/json.js";
4
+ import { openSSEStream } from "../shared/stream-helpers.js";
5
+ import { normalizeProviderStream } from "../shared/tool-stream-normalizer.js";
6
+ import { createStrictToolCallIdFactory } from "../shared/tool-call-ids.js";
7
+ import { toOpenAITools } from "../shared/tools.js";
8
+ import { applyCredits, makeUsage } from "../shared/usage.js";
9
+ const usageFromChunk = (chunk, options) => {
10
+ if (!chunk.usage)
11
+ return undefined;
12
+ return applyCredits(makeUsage(chunk.usage.prompt_tokens ?? 0, chunk.usage.completion_tokens ?? 0), options?.creditsPerInputToken, options?.creditsPerOutputToken);
13
+ };
14
+ const convertMessages = (messages, systemPrompt, options) => {
15
+ const out = [];
16
+ const pendingToolIds = new Map();
17
+ const makeStrictId = options?.normalizeToolCallIds === "strict9" ? createStrictToolCallIdFactory() : null;
18
+ if (systemPrompt)
19
+ out.push({ role: "system", content: systemPrompt });
20
+ for (const message of messages) {
21
+ if (message.role === "user") {
22
+ assertOnlySupportedFiles(message.content, true, "mistral");
23
+ const parts = message.content.map((part) => {
24
+ if (typeof part === "string")
25
+ return { type: "text", text: part };
26
+ if (part.type === "text")
27
+ return { type: "text", text: part.text };
28
+ return { type: "image_url", image_url: { url: `data:${part.mediaType};base64,${part.data}` } };
29
+ });
30
+ if (parts.length === 1 && parts[0]?.type === "text")
31
+ out.push({ role: "user", content: parts[0].text });
32
+ else
33
+ out.push({ role: "user", content: parts });
34
+ continue;
35
+ }
36
+ if (message.role === "assistant") {
37
+ let text = "";
38
+ const toolCalls = [];
39
+ for (const block of message.content) {
40
+ if (block.type === "text")
41
+ text += block.text;
42
+ else if (block.type === "tool_call") {
43
+ const mappedId = makeStrictId ? makeStrictId(block.id) : block.id;
44
+ if (makeStrictId) {
45
+ const queue = pendingToolIds.get(block.id) ?? [];
46
+ queue.push(mappedId);
47
+ pendingToolIds.set(block.id, queue);
48
+ }
49
+ toolCalls.push({
50
+ id: mappedId,
51
+ type: "function",
52
+ function: { name: block.name, arguments: stringifyJson(block.args) },
53
+ });
54
+ }
55
+ }
56
+ const next = { role: "assistant", content: text || null };
57
+ if (toolCalls.length > 0)
58
+ next.tool_calls = toolCalls;
59
+ out.push(next);
60
+ continue;
61
+ }
62
+ const queue = pendingToolIds.get(message.callId);
63
+ const mappedId = makeStrictId ? queue?.shift() : message.callId;
64
+ if (!mappedId)
65
+ continue;
66
+ if (makeStrictId && queue && queue.length === 0)
67
+ pendingToolIds.delete(message.callId);
68
+ out.push({
69
+ role: "tool",
70
+ content: stringifyJson(message.result),
71
+ name: message.name,
72
+ tool_call_id: mappedId,
73
+ });
74
+ }
75
+ return out;
76
+ };
77
+ const mapFinishReason = (reason, hasTools) => {
78
+ if (reason === "tool_calls")
79
+ return "tool_use";
80
+ if (reason === "length")
81
+ return "max_tokens";
82
+ if (hasTools)
83
+ return "tool_use";
84
+ return "stop";
85
+ };
86
+ const responseFormatName = (name) => {
87
+ const safe = (name ?? "structured_output").replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
88
+ return safe || "structured_output";
89
+ };
90
+ const applyResponseFormat = (body, request) => {
91
+ if (!request.responseFormat)
92
+ return;
93
+ body.response_format = {
94
+ type: "json_schema",
95
+ json_schema: {
96
+ name: responseFormatName(request.responseFormat.name),
97
+ schema: request.responseFormat.schema,
98
+ strict: true,
99
+ },
100
+ };
101
+ };
102
+ export const mistral = (model, options) => {
103
+ const baseURL = (options?.baseURL ?? "https://api.mistral.ai/v1").replace(/\/+$/, "");
104
+ const resolveTemperature = (request) => request.temperature ?? options?.temperature;
105
+ return {
106
+ name: "mistral",
107
+ family: "mistral",
108
+ model,
109
+ contextWindow: options?.contextWindow ?? 128_000,
110
+ capabilities: {
111
+ streaming: true,
112
+ tools: true,
113
+ images: true,
114
+ thinking: false,
115
+ usage: true,
116
+ structuredOutput: true,
117
+ },
118
+ async complete(request) {
119
+ const body = {
120
+ model,
121
+ messages: convertMessages(request.messages, request.systemPrompt, options),
122
+ stream: false,
123
+ };
124
+ if (request.tools?.length) {
125
+ body.tools = toOpenAITools(request.tools);
126
+ body.parallel_tool_calls = true;
127
+ }
128
+ applyResponseFormat(body, request);
129
+ const temperature = resolveTemperature(request);
130
+ if (temperature !== undefined)
131
+ body.temperature = temperature;
132
+ if (request.maxOutputTokens !== undefined)
133
+ body.max_tokens = request.maxOutputTokens;
134
+ const response = await fetch(`${baseURL}/chat/completions`, {
135
+ method: "POST",
136
+ headers: {
137
+ "Content-Type": "application/json",
138
+ Authorization: `Bearer ${options?.apiKey ?? globalThis.process?.env?.MISTRAL_API_KEY ?? ""}`,
139
+ },
140
+ body: JSON.stringify(body),
141
+ signal: request.signal,
142
+ }).catch((error) => {
143
+ throw new Error(formatConnectionError("mistral", error));
144
+ });
145
+ if (!response.ok) {
146
+ const normalized = await normalizeHttpError("mistral", response);
147
+ throw new Error(normalized.error);
148
+ }
149
+ const payload = safeJsonParse(await response.text());
150
+ if (!payload)
151
+ throw new Error("mistral returned invalid JSON.");
152
+ const choice = payload.choices?.[0];
153
+ const toolCalls = (choice?.message?.tool_calls ?? []).map((call, index) => ({
154
+ type: "tool_call",
155
+ id: call.id ?? `mistral-${index}`,
156
+ name: call.function?.name ?? "",
157
+ args: ensureRecord(safeJsonParse(call.function?.arguments ?? "{}")),
158
+ }));
159
+ const usage = usageFromChunk(payload, options);
160
+ const finishReason = mapFinishReason(choice?.finish_reason, toolCalls.length > 0);
161
+ return {
162
+ message: buildAssistantMessage(model, choice?.message?.content ?? "", "", toolCalls, usage, finishReason),
163
+ usage,
164
+ finishReason,
165
+ providerMeta: { model },
166
+ };
167
+ },
168
+ stream(request) {
169
+ const raw = async function* () {
170
+ const body = {
171
+ model,
172
+ messages: convertMessages(request.messages, request.systemPrompt, options),
173
+ stream: true,
174
+ };
175
+ if (request.tools?.length) {
176
+ body.tools = toOpenAITools(request.tools);
177
+ body.parallel_tool_calls = true;
178
+ }
179
+ applyResponseFormat(body, request);
180
+ const temperature = resolveTemperature(request);
181
+ if (temperature !== undefined)
182
+ body.temperature = temperature;
183
+ if (request.maxOutputTokens !== undefined)
184
+ body.max_tokens = request.maxOutputTokens;
185
+ const result = await openSSEStream(`${baseURL}/chat/completions`, {
186
+ "Content-Type": "application/json",
187
+ Authorization: `Bearer ${options?.apiKey ?? globalThis.process?.env?.MISTRAL_API_KEY ?? ""}`,
188
+ }, body, "mistral", request.signal, undefined, options?.timeouts);
189
+ if (!result.ok) {
190
+ yield result.error;
191
+ return;
192
+ }
193
+ const buffers = new Map();
194
+ let latestUsage;
195
+ let latestFinishReason;
196
+ const startToolCall = function* (buffer) {
197
+ if (buffer.started || !buffer.name.trim())
198
+ return;
199
+ buffer.started = true;
200
+ yield { type: "tool_start", callId: buffer.callId, name: buffer.name };
201
+ if (buffer.argsBuffer)
202
+ yield { type: "tool_delta", callId: buffer.callId, argsDelta: buffer.argsBuffer };
203
+ };
204
+ const flush = function* () {
205
+ for (const [, buffer] of buffers) {
206
+ yield* startToolCall(buffer);
207
+ yield {
208
+ type: "tool_call",
209
+ callId: buffer.callId,
210
+ name: buffer.name,
211
+ args: ensureRecord(safeJsonParse(buffer.argsBuffer || "{}")),
212
+ };
213
+ }
214
+ buffers.clear();
215
+ };
216
+ for await (const event of result.events) {
217
+ if (event.data === "[DONE]")
218
+ break;
219
+ const chunk = safeJsonParse(event.data);
220
+ if (!chunk)
221
+ continue;
222
+ const choice = chunk.choices?.[0];
223
+ const usage = usageFromChunk(chunk, options);
224
+ if (!choice) {
225
+ if (usage) {
226
+ latestUsage = usage;
227
+ yield { type: "usage", usage };
228
+ }
229
+ continue;
230
+ }
231
+ if (choice.delta.content)
232
+ yield { type: "text", delta: choice.delta.content };
233
+ if (choice.delta.tool_calls) {
234
+ for (const toolCall of choice.delta.tool_calls) {
235
+ const existing = buffers.get(toolCall.index);
236
+ if (!existing) {
237
+ const callId = toolCall.id ?? `mistral-${toolCall.index}`;
238
+ const name = toolCall.function?.name ?? "";
239
+ const argsDelta = toolCall.function?.arguments ?? "";
240
+ const buffer = {
241
+ callId,
242
+ name,
243
+ argsBuffer: argsDelta,
244
+ started: false,
245
+ };
246
+ buffers.set(toolCall.index, buffer);
247
+ yield* startToolCall(buffer);
248
+ }
249
+ else {
250
+ if (toolCall.function?.name)
251
+ existing.name = toolCall.function.name;
252
+ const argsDelta = toolCall.function?.arguments ?? "";
253
+ if (argsDelta)
254
+ existing.argsBuffer += argsDelta;
255
+ const wasStarted = existing.started;
256
+ yield* startToolCall(existing);
257
+ if (wasStarted && argsDelta) {
258
+ yield { type: "tool_delta", callId: existing.callId, argsDelta };
259
+ }
260
+ }
261
+ }
262
+ }
263
+ if (choice.finish_reason === "tool_calls") {
264
+ yield* flush();
265
+ }
266
+ latestFinishReason = mapFinishReason(choice.finish_reason, false);
267
+ if (usage) {
268
+ latestUsage = usage;
269
+ yield { type: "usage", usage };
270
+ }
271
+ }
272
+ if (buffers.size > 0) {
273
+ latestFinishReason = "tool_use";
274
+ yield* flush();
275
+ }
276
+ if (latestFinishReason) {
277
+ yield {
278
+ type: "usage",
279
+ usage: latestUsage ?? makeUsage(),
280
+ finishReason: latestFinishReason,
281
+ };
282
+ }
283
+ };
284
+ return normalizeProviderStream(raw(), { suppressTextAfterMalformedTool: true });
285
+ },
286
+ };
287
+ };
@@ -0,0 +1,10 @@
1
+ import type { Provider, ProviderTimeouts } from "../types.js";
2
+ export type OllamaOptions = {
3
+ baseURL?: string;
4
+ contextWindow?: number;
5
+ temperature?: number;
6
+ creditsPerInputToken?: number;
7
+ creditsPerOutputToken?: number;
8
+ timeouts?: ProviderTimeouts;
9
+ };
10
+ export declare const ollama: (model: string, options?: OllamaOptions) => Provider;
@@ -0,0 +1,241 @@
1
+ import { formatConnectionError, normalizeHttpError } from "../shared/errors.js";
2
+ import { assertOnlySupportedFiles, buildAssistantMessage } from "../shared/messages.js";
3
+ import { parseNDJSON } from "../shared/ndjson.js";
4
+ import { ensureRecord, safeJsonParse, stringifyJson } from "../shared/json.js";
5
+ import { normalizeProviderStream } from "../shared/tool-stream-normalizer.js";
6
+ import { toOllamaTools } from "../shared/tools.js";
7
+ import { applyCredits, makeUsage } from "../shared/usage.js";
8
+ const convertMessages = (messages, systemPrompt) => {
9
+ const out = [];
10
+ if (systemPrompt)
11
+ out.push({ role: "system", content: systemPrompt });
12
+ for (const message of messages) {
13
+ if (message.role === "user") {
14
+ assertOnlySupportedFiles(message.content, true, "ollama");
15
+ let text = "";
16
+ const images = [];
17
+ for (const part of message.content) {
18
+ if (typeof part === "string")
19
+ text += part;
20
+ else if (part.type === "text")
21
+ text += part.text;
22
+ else
23
+ images.push(part.data);
24
+ }
25
+ const next = { role: "user", content: text };
26
+ if (images.length > 0)
27
+ next.images = images;
28
+ out.push(next);
29
+ }
30
+ else if (message.role === "assistant") {
31
+ let text = "";
32
+ const toolCalls = [];
33
+ for (const block of message.content) {
34
+ if (block.type === "text")
35
+ text += block.text;
36
+ else if (block.type === "tool_call") {
37
+ toolCalls.push({ function: { name: block.name, arguments: block.args } });
38
+ }
39
+ }
40
+ const next = { role: "assistant", content: text };
41
+ if (toolCalls.length > 0)
42
+ next.tool_calls = toolCalls;
43
+ out.push(next);
44
+ }
45
+ else {
46
+ out.push({
47
+ role: "tool",
48
+ name: message.name,
49
+ tool_call_id: message.callId,
50
+ content: stringifyJson({
51
+ tool_call_id: message.callId,
52
+ name: message.name,
53
+ result: message.result,
54
+ }),
55
+ });
56
+ }
57
+ }
58
+ return out;
59
+ };
60
+ const usageFromResponse = (response, options) => applyCredits(makeUsage(response.prompt_eval_count ?? 0, response.eval_count ?? 0), options?.creditsPerInputToken, options?.creditsPerOutputToken);
61
+ const toolCallsFromResponse = (response) => (response.message?.tool_calls ?? []).map((toolCall, index) => ({
62
+ type: "tool_call",
63
+ id: `ollama-${index}`,
64
+ name: toolCall.function.name,
65
+ args: toolCall.function.arguments,
66
+ }));
67
+ export const ollama = (model, options) => {
68
+ const baseURL = (options?.baseURL ?? "http://localhost:11434").replace(/\/+$/, "");
69
+ const contextWindow = options?.contextWindow ?? 128_000;
70
+ const resolveTemperature = (request) => request.temperature ?? options?.temperature;
71
+ return {
72
+ name: "ollama",
73
+ family: "ollama",
74
+ model,
75
+ contextWindow,
76
+ capabilities: {
77
+ streaming: true,
78
+ tools: true,
79
+ images: true,
80
+ thinking: false,
81
+ usage: true,
82
+ structuredOutput: true,
83
+ },
84
+ async complete(request) {
85
+ const body = {
86
+ model,
87
+ messages: convertMessages(request.messages, request.systemPrompt),
88
+ stream: false,
89
+ };
90
+ if (request.tools?.length)
91
+ body.tools = toOllamaTools(request.tools);
92
+ if (request.responseFormat)
93
+ body.format = request.responseFormat.schema;
94
+ const temperature = resolveTemperature(request);
95
+ if (temperature !== undefined)
96
+ body.options = { temperature };
97
+ const response = await fetch(`${baseURL}/api/chat`, {
98
+ method: "POST",
99
+ headers: { "Content-Type": "application/json" },
100
+ body: JSON.stringify(body),
101
+ signal: request.signal,
102
+ }).catch((error) => {
103
+ throw new Error(formatConnectionError("ollama", error));
104
+ });
105
+ if (!response.ok) {
106
+ const normalized = await normalizeHttpError("ollama", response);
107
+ throw new Error(normalized.error);
108
+ }
109
+ const payload = safeJsonParse(await response.text());
110
+ if (!payload)
111
+ throw new Error("ollama returned invalid JSON.");
112
+ const usage = usageFromResponse(payload, options);
113
+ const toolCalls = toolCallsFromResponse(payload);
114
+ const finishReason = toolCalls.length > 0 ? "tool_use" : "stop";
115
+ return {
116
+ message: buildAssistantMessage(model, payload.message?.content ?? "", "", toolCalls, usage, finishReason),
117
+ usage,
118
+ finishReason,
119
+ providerMeta: { model },
120
+ };
121
+ },
122
+ stream(request) {
123
+ const raw = async function* () {
124
+ const body = {
125
+ model,
126
+ messages: convertMessages(request.messages, request.systemPrompt),
127
+ stream: true,
128
+ };
129
+ if (request.tools?.length)
130
+ body.tools = toOllamaTools(request.tools);
131
+ if (request.responseFormat)
132
+ body.format = request.responseFormat.schema;
133
+ const temperature = resolveTemperature(request);
134
+ if (temperature !== undefined)
135
+ body.options = { temperature };
136
+ let response;
137
+ const controller = new AbortController();
138
+ const abortExternal = () => controller.abort(request.signal?.reason);
139
+ const cleanupExternalAbort = () => {
140
+ if (request.signal)
141
+ request.signal.removeEventListener("abort", abortExternal);
142
+ };
143
+ if (request.signal) {
144
+ if (request.signal.aborted)
145
+ controller.abort(request.signal.reason);
146
+ else
147
+ request.signal.addEventListener("abort", abortExternal, { once: true });
148
+ }
149
+ let firstByteTimeout;
150
+ const firstByteDeadline = options?.timeouts?.firstByteMs && options.timeouts.firstByteMs > 0
151
+ ? Date.now() + options.timeouts.firstByteMs
152
+ : undefined;
153
+ try {
154
+ response = await Promise.race([
155
+ fetch(`${baseURL}/api/chat`, {
156
+ method: "POST",
157
+ headers: { "Content-Type": "application/json" },
158
+ body: JSON.stringify(body),
159
+ signal: controller.signal,
160
+ }),
161
+ new Promise((_, reject) => {
162
+ if (!options?.timeouts?.firstByteMs || options.timeouts.firstByteMs <= 0)
163
+ return;
164
+ firstByteTimeout = setTimeout(() => {
165
+ controller.abort();
166
+ reject({
167
+ scope: "provider_first_byte",
168
+ message: `ollama first byte timeout after ${options.timeouts.firstByteMs}ms.`,
169
+ });
170
+ }, options.timeouts.firstByteMs);
171
+ }),
172
+ ]);
173
+ }
174
+ catch (error) {
175
+ cleanupExternalAbort();
176
+ if (error && typeof error === "object" && error.scope === "provider_first_byte") {
177
+ yield {
178
+ type: "timeout",
179
+ scope: "provider_first_byte",
180
+ message: String(error.message ?? "ollama first byte timeout"),
181
+ retryable: true,
182
+ };
183
+ return;
184
+ }
185
+ yield {
186
+ type: "error",
187
+ error: formatConnectionError("ollama", error),
188
+ retryable: true,
189
+ };
190
+ return;
191
+ }
192
+ finally {
193
+ if (firstByteTimeout)
194
+ clearTimeout(firstByteTimeout);
195
+ }
196
+ if (!response.ok) {
197
+ const normalized = await normalizeHttpError("ollama", response);
198
+ cleanupExternalAbort();
199
+ yield { type: "error", ...normalized };
200
+ return;
201
+ }
202
+ const reader = response.body?.getReader();
203
+ if (!reader) {
204
+ cleanupExternalAbort();
205
+ yield { type: "error", error: "ollama response body missing", retryable: false };
206
+ return;
207
+ }
208
+ let toolCounter = 0;
209
+ const streamTimeouts = options?.timeouts ? { ...options.timeouts } : undefined;
210
+ if (firstByteDeadline && streamTimeouts) {
211
+ streamTimeouts.firstByteMs = Math.max(1, firstByteDeadline - Date.now());
212
+ }
213
+ try {
214
+ for await (const chunk of parseNDJSON(reader, streamTimeouts)) {
215
+ if (chunk.message?.content)
216
+ yield { type: "text", delta: chunk.message.content };
217
+ for (const toolCall of chunk.message?.tool_calls ?? []) {
218
+ const callId = `ollama-${toolCounter++}`;
219
+ yield { type: "tool_start", callId, name: toolCall.function.name };
220
+ yield {
221
+ type: "tool_call",
222
+ callId,
223
+ name: toolCall.function.name,
224
+ args: ensureRecord(toolCall.function.arguments),
225
+ };
226
+ }
227
+ if (chunk.done) {
228
+ yield { type: "usage", usage: usageFromResponse(chunk, options) };
229
+ }
230
+ }
231
+ }
232
+ finally {
233
+ cleanupExternalAbort();
234
+ controller.abort();
235
+ await reader.cancel().catch(() => { });
236
+ }
237
+ };
238
+ return normalizeProviderStream(raw(), { suppressTextAfterMalformedTool: true });
239
+ },
240
+ };
241
+ };
@@ -0,0 +1,2 @@
1
+ import type { OpenAICompatibleConfig, Provider } from "../types.js";
2
+ export declare const openAICompatible: (config: OpenAICompatibleConfig) => Provider;