@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.
- package/LICENSE +21 -0
- package/README.md +251 -0
- package/aggregates.d.ts +7 -0
- package/aggregates.js +115 -0
- package/ai/complete-from-stream.d.ts +2 -0
- package/ai/complete-from-stream.js +36 -0
- package/ai/index.d.ts +10 -0
- package/ai/index.js +9 -0
- package/ai/providers/anthropic.d.ts +13 -0
- package/ai/providers/anthropic.js +266 -0
- package/ai/providers/gemini.d.ts +12 -0
- package/ai/providers/gemini.js +192 -0
- package/ai/providers/mistral.d.ts +12 -0
- package/ai/providers/mistral.js +287 -0
- package/ai/providers/ollama.d.ts +10 -0
- package/ai/providers/ollama.js +241 -0
- package/ai/providers/openai-compatible.d.ts +2 -0
- package/ai/providers/openai-compatible.js +349 -0
- package/ai/providers/openai.d.ts +12 -0
- package/ai/providers/openai.js +22 -0
- package/ai/providers/openrouter.d.ts +13 -0
- package/ai/providers/openrouter.js +28 -0
- package/ai/providers/vllm.d.ts +11 -0
- package/ai/providers/vllm.js +22 -0
- package/ai/shared/errors.d.ts +15 -0
- package/ai/shared/errors.js +56 -0
- package/ai/shared/json.d.ts +3 -0
- package/ai/shared/json.js +15 -0
- package/ai/shared/messages.d.ts +15 -0
- package/ai/shared/messages.js +58 -0
- package/ai/shared/ndjson.d.ts +4 -0
- package/ai/shared/ndjson.js +60 -0
- package/ai/shared/sse.d.ts +15 -0
- package/ai/shared/sse.js +79 -0
- package/ai/shared/stream-helpers.d.ts +13 -0
- package/ai/shared/stream-helpers.js +105 -0
- package/ai/shared/tool-call-ids.d.ts +5 -0
- package/ai/shared/tool-call-ids.js +38 -0
- package/ai/shared/tool-stream-normalizer.d.ts +6 -0
- package/ai/shared/tool-stream-normalizer.js +271 -0
- package/ai/shared/tools.d.ts +29 -0
- package/ai/shared/tools.js +25 -0
- package/ai/shared/usage.d.ts +3 -0
- package/ai/shared/usage.js +5 -0
- package/ai/types.d.ts +252 -0
- package/ai/types.js +0 -0
- package/compact.d.ts +5 -0
- package/compact.js +108 -0
- package/index.d.ts +11 -0
- package/index.js +12 -0
- package/nessi.d.ts +2 -0
- package/nessi.js +1250 -0
- package/package.json +80 -0
- package/providers/ollama.d.ts +2 -0
- package/providers/ollama.js +1 -0
- package/providers/openai.d.ts +2 -0
- package/providers/openai.js +1 -0
- package/providers/openrouter.d.ts +2 -0
- package/providers/openrouter.js +1 -0
- package/stores.d.ts +11 -0
- package/stores.js +42 -0
- package/structured.d.ts +9 -0
- package/structured.js +413 -0
- package/tools.d.ts +25 -0
- package/tools.js +36 -0
- package/types.d.ts +290 -0
- package/types.js +3 -0
- package/utils.d.ts +15 -0
- package/utils.js +47 -0
|
@@ -0,0 +1,266 @@
|
|
|
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 { toAnthropicTools } from "../shared/tools.js";
|
|
7
|
+
import { applyCredits, makeUsage } from "../shared/usage.js";
|
|
8
|
+
const mapFinishReason = (reason, hasTools) => {
|
|
9
|
+
if (reason === "tool_use")
|
|
10
|
+
return "tool_use";
|
|
11
|
+
if (reason === "max_tokens")
|
|
12
|
+
return "max_tokens";
|
|
13
|
+
if (hasTools)
|
|
14
|
+
return "tool_use";
|
|
15
|
+
return "stop";
|
|
16
|
+
};
|
|
17
|
+
const pushMessage = (messages, next) => {
|
|
18
|
+
const last = messages[messages.length - 1];
|
|
19
|
+
if (last?.role === next.role) {
|
|
20
|
+
last.content.push(...next.content);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
messages.push(next);
|
|
24
|
+
};
|
|
25
|
+
const convertMessages = (messages) => {
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const message of messages) {
|
|
28
|
+
if (message.role === "user") {
|
|
29
|
+
assertOnlySupportedFiles(message.content, true, "anthropic");
|
|
30
|
+
const content = [];
|
|
31
|
+
for (const part of message.content) {
|
|
32
|
+
if (typeof part === "string")
|
|
33
|
+
content.push({ type: "text", text: part });
|
|
34
|
+
else if (part.type === "text")
|
|
35
|
+
content.push({ type: "text", text: part.text });
|
|
36
|
+
else
|
|
37
|
+
content.push({
|
|
38
|
+
type: "image",
|
|
39
|
+
source: { type: "base64", media_type: part.mediaType, data: part.data },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
pushMessage(out, { role: "user", content });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (message.role === "assistant") {
|
|
46
|
+
const content = [];
|
|
47
|
+
for (const block of message.content) {
|
|
48
|
+
if (block.type === "text")
|
|
49
|
+
content.push({ type: "text", text: block.text });
|
|
50
|
+
else if (block.type === "tool_call") {
|
|
51
|
+
content.push({
|
|
52
|
+
type: "tool_use",
|
|
53
|
+
id: block.id,
|
|
54
|
+
name: block.name,
|
|
55
|
+
input: block.args,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
pushMessage(out, { role: "assistant", content });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
pushMessage(out, {
|
|
63
|
+
role: "user",
|
|
64
|
+
content: [{
|
|
65
|
+
type: "tool_result",
|
|
66
|
+
tool_use_id: message.callId,
|
|
67
|
+
content: stringifyJson(message.result),
|
|
68
|
+
is_error: message.isError,
|
|
69
|
+
}],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
};
|
|
74
|
+
const resolveTemperature = (request, options) => request.temperature ?? options?.temperature;
|
|
75
|
+
const usageFromValue = (usage, options) => applyCredits(makeUsage(usage?.input_tokens ?? 0, usage?.output_tokens ?? 0), options?.creditsPerInputToken, options?.creditsPerOutputToken);
|
|
76
|
+
const mergeUsage = (current, usage, options) => {
|
|
77
|
+
if (!usage)
|
|
78
|
+
return current;
|
|
79
|
+
return applyCredits(makeUsage(usage.input_tokens ?? current.input, usage.output_tokens ?? current.output), options?.creditsPerInputToken, options?.creditsPerOutputToken);
|
|
80
|
+
};
|
|
81
|
+
const applyResponseFormat = (body, request) => {
|
|
82
|
+
if (!request.responseFormat)
|
|
83
|
+
return;
|
|
84
|
+
body.output_config = {
|
|
85
|
+
format: {
|
|
86
|
+
type: "json_schema",
|
|
87
|
+
schema: request.responseFormat.schema,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
export const anthropic = (model, options) => {
|
|
92
|
+
const baseURL = (options?.baseURL ?? "https://api.anthropic.com").replace(/\/+$/, "");
|
|
93
|
+
const apiVersion = options?.apiVersion ?? "2023-06-01";
|
|
94
|
+
const maxOutputTokens = options?.maxOutputTokens ?? 1024;
|
|
95
|
+
return {
|
|
96
|
+
name: "anthropic",
|
|
97
|
+
family: "anthropic",
|
|
98
|
+
model,
|
|
99
|
+
contextWindow: options?.contextWindow ?? 200_000,
|
|
100
|
+
capabilities: {
|
|
101
|
+
streaming: true,
|
|
102
|
+
tools: true,
|
|
103
|
+
images: true,
|
|
104
|
+
thinking: false,
|
|
105
|
+
usage: true,
|
|
106
|
+
structuredOutput: true,
|
|
107
|
+
},
|
|
108
|
+
async complete(request) {
|
|
109
|
+
const body = {
|
|
110
|
+
model,
|
|
111
|
+
system: request.systemPrompt,
|
|
112
|
+
messages: convertMessages(request.messages),
|
|
113
|
+
max_tokens: request.maxOutputTokens ?? maxOutputTokens,
|
|
114
|
+
};
|
|
115
|
+
if (request.tools?.length)
|
|
116
|
+
body.tools = toAnthropicTools(request.tools);
|
|
117
|
+
applyResponseFormat(body, request);
|
|
118
|
+
const temperature = resolveTemperature(request, options);
|
|
119
|
+
if (temperature !== undefined)
|
|
120
|
+
body.temperature = temperature;
|
|
121
|
+
const response = await fetch(`${baseURL}/v1/messages`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: {
|
|
124
|
+
"Content-Type": "application/json",
|
|
125
|
+
"x-api-key": options?.apiKey ?? globalThis.process?.env?.ANTHROPIC_API_KEY ?? "",
|
|
126
|
+
"anthropic-version": apiVersion,
|
|
127
|
+
},
|
|
128
|
+
body: JSON.stringify(body),
|
|
129
|
+
signal: request.signal,
|
|
130
|
+
}).catch((error) => {
|
|
131
|
+
throw new Error(formatConnectionError("anthropic", error));
|
|
132
|
+
});
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
const normalized = await normalizeHttpError("anthropic", response);
|
|
135
|
+
throw new Error(normalized.error);
|
|
136
|
+
}
|
|
137
|
+
const payload = safeJsonParse(await response.text());
|
|
138
|
+
if (!payload)
|
|
139
|
+
throw new Error("anthropic returned invalid JSON.");
|
|
140
|
+
const text = (payload.content ?? [])
|
|
141
|
+
.filter((block) => block.type === "text")
|
|
142
|
+
.map((block) => block.text ?? "")
|
|
143
|
+
.join("");
|
|
144
|
+
const toolCalls = (payload.content ?? [])
|
|
145
|
+
.filter((block) => block.type === "tool_use")
|
|
146
|
+
.map((block) => ({
|
|
147
|
+
type: "tool_call",
|
|
148
|
+
id: block.id,
|
|
149
|
+
name: block.name,
|
|
150
|
+
args: block.input ?? {},
|
|
151
|
+
}));
|
|
152
|
+
const usage = usageFromValue(payload.usage, options);
|
|
153
|
+
const finishReason = mapFinishReason(payload.stop_reason, toolCalls.length > 0);
|
|
154
|
+
return {
|
|
155
|
+
message: buildAssistantMessage(model, text, "", toolCalls, usage, finishReason),
|
|
156
|
+
usage,
|
|
157
|
+
finishReason,
|
|
158
|
+
providerMeta: { model, requestId: payload.id },
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
stream(request) {
|
|
162
|
+
const raw = async function* () {
|
|
163
|
+
const body = {
|
|
164
|
+
model,
|
|
165
|
+
system: request.systemPrompt,
|
|
166
|
+
messages: convertMessages(request.messages),
|
|
167
|
+
max_tokens: request.maxOutputTokens ?? maxOutputTokens,
|
|
168
|
+
stream: true,
|
|
169
|
+
};
|
|
170
|
+
if (request.tools?.length)
|
|
171
|
+
body.tools = toAnthropicTools(request.tools);
|
|
172
|
+
applyResponseFormat(body, request);
|
|
173
|
+
const temperature = resolveTemperature(request, options);
|
|
174
|
+
if (temperature !== undefined)
|
|
175
|
+
body.temperature = temperature;
|
|
176
|
+
const result = await openSSEStream(`${baseURL}/v1/messages`, {
|
|
177
|
+
"Content-Type": "application/json",
|
|
178
|
+
"x-api-key": options?.apiKey ?? globalThis.process?.env?.ANTHROPIC_API_KEY ?? "",
|
|
179
|
+
"anthropic-version": apiVersion,
|
|
180
|
+
}, body, "anthropic", request.signal, undefined, options?.timeouts);
|
|
181
|
+
if (!result.ok) {
|
|
182
|
+
yield result.error;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const toolBuffers = new Map();
|
|
186
|
+
let latestUsage = makeUsage();
|
|
187
|
+
let latestFinishReason;
|
|
188
|
+
let syntheticIndex = 0;
|
|
189
|
+
let sawToolCall = false;
|
|
190
|
+
for await (const event of result.events) {
|
|
191
|
+
if (event.data === "[DONE]")
|
|
192
|
+
break;
|
|
193
|
+
const payload = safeJsonParse(event.data);
|
|
194
|
+
if (!payload)
|
|
195
|
+
continue;
|
|
196
|
+
if (event.event === "message_start" && payload.message?.usage) {
|
|
197
|
+
latestUsage = mergeUsage(latestUsage, payload.message.usage, options);
|
|
198
|
+
}
|
|
199
|
+
if (event.event === "content_block_start" && payload.content_block?.type === "tool_use") {
|
|
200
|
+
const index = typeof payload.index === "number" ? payload.index : syntheticIndex++;
|
|
201
|
+
const startInput = payload.content_block.input;
|
|
202
|
+
const argsBuffer = startInput && Object.keys(startInput).length > 0 ? JSON.stringify(startInput) : "";
|
|
203
|
+
toolBuffers.set(index, {
|
|
204
|
+
callId: payload.content_block.id ?? `anthropic-${index}`,
|
|
205
|
+
name: payload.content_block.name ?? "",
|
|
206
|
+
argsBuffer,
|
|
207
|
+
});
|
|
208
|
+
sawToolCall = true;
|
|
209
|
+
yield {
|
|
210
|
+
type: "tool_start",
|
|
211
|
+
callId: payload.content_block.id ?? `anthropic-${index}`,
|
|
212
|
+
name: payload.content_block.name ?? "",
|
|
213
|
+
};
|
|
214
|
+
if (argsBuffer) {
|
|
215
|
+
yield {
|
|
216
|
+
type: "tool_delta",
|
|
217
|
+
callId: payload.content_block.id ?? `anthropic-${index}`,
|
|
218
|
+
argsDelta: argsBuffer,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (event.event === "content_block_delta") {
|
|
223
|
+
if (payload.delta?.type === "text_delta" && payload.delta.text) {
|
|
224
|
+
yield { type: "text", delta: payload.delta.text };
|
|
225
|
+
}
|
|
226
|
+
else if (payload.delta?.type === "input_json_delta") {
|
|
227
|
+
if (typeof payload.index !== "number")
|
|
228
|
+
continue;
|
|
229
|
+
const index = payload.index;
|
|
230
|
+
const existing = toolBuffers.get(index);
|
|
231
|
+
if (existing && payload.delta.partial_json) {
|
|
232
|
+
existing.argsBuffer += payload.delta.partial_json;
|
|
233
|
+
yield { type: "tool_delta", callId: existing.callId, argsDelta: payload.delta.partial_json };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (event.event === "content_block_stop") {
|
|
238
|
+
if (typeof payload.index !== "number")
|
|
239
|
+
continue;
|
|
240
|
+
const index = payload.index;
|
|
241
|
+
const existing = toolBuffers.get(index);
|
|
242
|
+
if (existing) {
|
|
243
|
+
yield {
|
|
244
|
+
type: "tool_call",
|
|
245
|
+
callId: existing.callId,
|
|
246
|
+
name: existing.name,
|
|
247
|
+
args: ensureRecord(safeJsonParse(existing.argsBuffer || "{}")),
|
|
248
|
+
};
|
|
249
|
+
toolBuffers.delete(index);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (event.event === "message_delta" && payload.usage) {
|
|
253
|
+
latestUsage = mergeUsage(latestUsage, payload.usage, options);
|
|
254
|
+
}
|
|
255
|
+
if (event.event === "message_delta" && payload.delta?.stop_reason) {
|
|
256
|
+
latestFinishReason = mapFinishReason(payload.delta.stop_reason, sawToolCall || toolBuffers.size > 0);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (latestUsage.total > 0 || latestFinishReason) {
|
|
260
|
+
yield { type: "usage", usage: latestUsage, finishReason: latestFinishReason };
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
return normalizeProviderStream(raw(), { suppressTextAfterMalformedTool: true });
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Provider, ProviderTimeouts } from "../types.js";
|
|
2
|
+
export type GeminiOptions = {
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
baseURL?: string;
|
|
5
|
+
contextWindow?: number;
|
|
6
|
+
temperature?: number;
|
|
7
|
+
maxOutputTokens?: number;
|
|
8
|
+
creditsPerInputToken?: number;
|
|
9
|
+
creditsPerOutputToken?: number;
|
|
10
|
+
timeouts?: ProviderTimeouts;
|
|
11
|
+
};
|
|
12
|
+
export declare const gemini: (model: string, options?: GeminiOptions) => Provider;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { formatConnectionError, normalizeHttpError } from "../shared/errors.js";
|
|
2
|
+
import { assertOnlySupportedFiles, buildAssistantMessage } from "../shared/messages.js";
|
|
3
|
+
import { ensureRecord, safeJsonParse } from "../shared/json.js";
|
|
4
|
+
import { openSSEStream } from "../shared/stream-helpers.js";
|
|
5
|
+
import { normalizeProviderStream } from "../shared/tool-stream-normalizer.js";
|
|
6
|
+
import { toGeminiTools } from "../shared/tools.js";
|
|
7
|
+
import { applyCredits, makeUsage } from "../shared/usage.js";
|
|
8
|
+
const convertMessages = (messages) => {
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const message of messages) {
|
|
11
|
+
if (message.role === "user") {
|
|
12
|
+
assertOnlySupportedFiles(message.content, true, "gemini");
|
|
13
|
+
out.push({
|
|
14
|
+
role: "user",
|
|
15
|
+
parts: message.content.map((part) => {
|
|
16
|
+
if (typeof part === "string")
|
|
17
|
+
return { text: part };
|
|
18
|
+
if (part.type === "text")
|
|
19
|
+
return { text: part.text };
|
|
20
|
+
return { inlineData: { mimeType: part.mediaType, data: part.data } };
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (message.role === "assistant") {
|
|
26
|
+
const parts = [];
|
|
27
|
+
for (const block of message.content) {
|
|
28
|
+
if (block.type === "text")
|
|
29
|
+
parts.push({ text: block.text });
|
|
30
|
+
else if (block.type === "tool_call") {
|
|
31
|
+
parts.push({ functionCall: { name: block.name, args: block.args } });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
out.push({ role: "model", parts });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
out.push({
|
|
38
|
+
role: "user",
|
|
39
|
+
parts: [{
|
|
40
|
+
functionResponse: {
|
|
41
|
+
name: message.name,
|
|
42
|
+
response: ensureRecord(message.result),
|
|
43
|
+
},
|
|
44
|
+
}],
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
};
|
|
49
|
+
const usageFromResponse = (response, options) => applyCredits(makeUsage(response.usageMetadata?.promptTokenCount ?? 0, response.usageMetadata?.candidatesTokenCount ?? 0), options?.creditsPerInputToken, options?.creditsPerOutputToken);
|
|
50
|
+
const mapFinishReason = (reason, hasTools) => {
|
|
51
|
+
if (reason === "MAX_TOKENS")
|
|
52
|
+
return "max_tokens";
|
|
53
|
+
if (hasTools)
|
|
54
|
+
return "tool_use";
|
|
55
|
+
return "stop";
|
|
56
|
+
};
|
|
57
|
+
const createRequestId = () => globalThis.crypto?.randomUUID?.() ?? `gemini-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
58
|
+
export const gemini = (model, options) => {
|
|
59
|
+
const baseURL = (options?.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/models").replace(/\/+$/, "");
|
|
60
|
+
const apiKey = options?.apiKey ?? globalThis.process?.env?.GEMINI_API_KEY ?? globalThis.process?.env?.GOOGLE_API_KEY;
|
|
61
|
+
const urlFor = (path) => {
|
|
62
|
+
const key = apiKey ? `?key=${encodeURIComponent(apiKey)}` : "";
|
|
63
|
+
const alt = path === "streamGenerateContent" ? `${key ? "&" : "?"}alt=sse` : "";
|
|
64
|
+
return `${baseURL}/${model}:${path}${key}${alt}`;
|
|
65
|
+
};
|
|
66
|
+
const buildBody = (request) => {
|
|
67
|
+
const body = {
|
|
68
|
+
contents: convertMessages(request.messages),
|
|
69
|
+
};
|
|
70
|
+
if (request.systemPrompt) {
|
|
71
|
+
body.systemInstruction = { parts: [{ text: request.systemPrompt }] };
|
|
72
|
+
}
|
|
73
|
+
if (request.tools?.length)
|
|
74
|
+
body.tools = toGeminiTools(request.tools);
|
|
75
|
+
const generationConfig = {};
|
|
76
|
+
const temperature = request.temperature ?? options?.temperature;
|
|
77
|
+
if (temperature !== undefined)
|
|
78
|
+
generationConfig.temperature = temperature;
|
|
79
|
+
const maxOutputTokens = request.maxOutputTokens ?? options?.maxOutputTokens;
|
|
80
|
+
if (maxOutputTokens !== undefined)
|
|
81
|
+
generationConfig.maxOutputTokens = maxOutputTokens;
|
|
82
|
+
if (request.disableReasoning)
|
|
83
|
+
generationConfig.thinkingConfig = { thinkingBudget: 0 };
|
|
84
|
+
if (request.responseFormat) {
|
|
85
|
+
generationConfig.responseMimeType = "application/json";
|
|
86
|
+
generationConfig.responseJsonSchema = request.responseFormat.schema;
|
|
87
|
+
}
|
|
88
|
+
if (Object.keys(generationConfig).length > 0)
|
|
89
|
+
body.generationConfig = generationConfig;
|
|
90
|
+
return body;
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
name: "gemini",
|
|
94
|
+
family: "gemini",
|
|
95
|
+
model,
|
|
96
|
+
contextWindow: options?.contextWindow ?? 1_000_000,
|
|
97
|
+
capabilities: {
|
|
98
|
+
streaming: true,
|
|
99
|
+
tools: true,
|
|
100
|
+
images: true,
|
|
101
|
+
thinking: false,
|
|
102
|
+
usage: true,
|
|
103
|
+
structuredOutput: true,
|
|
104
|
+
},
|
|
105
|
+
async complete(request) {
|
|
106
|
+
const requestId = createRequestId();
|
|
107
|
+
const response = await fetch(urlFor("generateContent"), {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: { "Content-Type": "application/json" },
|
|
110
|
+
body: JSON.stringify(buildBody(request)),
|
|
111
|
+
signal: request.signal,
|
|
112
|
+
}).catch((error) => {
|
|
113
|
+
throw new Error(formatConnectionError("gemini", error));
|
|
114
|
+
});
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
const normalized = await normalizeHttpError("gemini", response);
|
|
117
|
+
throw new Error(normalized.error);
|
|
118
|
+
}
|
|
119
|
+
const payload = safeJsonParse(await response.text());
|
|
120
|
+
if (!payload)
|
|
121
|
+
throw new Error("gemini returned invalid JSON.");
|
|
122
|
+
const candidate = payload.candidates?.[0];
|
|
123
|
+
const parts = candidate?.content?.parts ?? [];
|
|
124
|
+
const text = parts.map((part) => part.text ?? "").join("");
|
|
125
|
+
const toolCalls = parts
|
|
126
|
+
.filter((part) => Boolean(part.functionCall))
|
|
127
|
+
.map((part, index) => ({
|
|
128
|
+
type: "tool_call",
|
|
129
|
+
id: `${requestId}-${index}`,
|
|
130
|
+
name: part.functionCall.name,
|
|
131
|
+
args: part.functionCall.args ?? {},
|
|
132
|
+
}));
|
|
133
|
+
const usage = usageFromResponse(payload, options);
|
|
134
|
+
const finishReason = mapFinishReason(candidate?.finishReason, toolCalls.length > 0);
|
|
135
|
+
return {
|
|
136
|
+
message: buildAssistantMessage(model, text, "", toolCalls, usage, finishReason),
|
|
137
|
+
usage,
|
|
138
|
+
finishReason,
|
|
139
|
+
providerMeta: { model },
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
stream(request) {
|
|
143
|
+
const raw = async function* () {
|
|
144
|
+
const result = await openSSEStream(urlFor("streamGenerateContent"), { "Content-Type": "application/json" }, buildBody(request), "gemini", request.signal, undefined, options?.timeouts);
|
|
145
|
+
if (!result.ok) {
|
|
146
|
+
yield result.error;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
let toolCounter = 0;
|
|
150
|
+
const requestId = createRequestId();
|
|
151
|
+
let latestUsage;
|
|
152
|
+
let latestFinishReason;
|
|
153
|
+
for await (const event of result.events) {
|
|
154
|
+
if (event.data === "[DONE]")
|
|
155
|
+
break;
|
|
156
|
+
const payload = safeJsonParse(event.data);
|
|
157
|
+
if (!payload)
|
|
158
|
+
continue;
|
|
159
|
+
const candidate = payload.candidates?.[0];
|
|
160
|
+
const parts = candidate?.content?.parts ?? [];
|
|
161
|
+
latestFinishReason = mapFinishReason(candidate?.finishReason, parts.some((part) => Boolean(part.functionCall)));
|
|
162
|
+
for (const part of parts) {
|
|
163
|
+
if (part.text)
|
|
164
|
+
yield { type: "text", delta: part.text };
|
|
165
|
+
if (part.functionCall) {
|
|
166
|
+
const callId = `${requestId}-${toolCounter++}`;
|
|
167
|
+
yield { type: "tool_start", callId, name: part.functionCall.name };
|
|
168
|
+
yield {
|
|
169
|
+
type: "tool_call",
|
|
170
|
+
callId,
|
|
171
|
+
name: part.functionCall.name,
|
|
172
|
+
args: part.functionCall.args ?? {},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const usage = usageFromResponse(payload, options);
|
|
177
|
+
latestUsage = usage;
|
|
178
|
+
if (usage.total > 0)
|
|
179
|
+
yield { type: "usage", usage };
|
|
180
|
+
}
|
|
181
|
+
if (latestFinishReason) {
|
|
182
|
+
yield {
|
|
183
|
+
type: "usage",
|
|
184
|
+
usage: latestUsage ?? makeUsage(),
|
|
185
|
+
finishReason: latestFinishReason,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
return normalizeProviderStream(raw(), { suppressTextAfterMalformedTool: true });
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Provider, ProviderTimeouts } from "../types.js";
|
|
2
|
+
export type MistralOptions = {
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
baseURL?: string;
|
|
5
|
+
contextWindow?: number;
|
|
6
|
+
temperature?: number;
|
|
7
|
+
normalizeToolCallIds?: "strict9" | "never";
|
|
8
|
+
creditsPerInputToken?: number;
|
|
9
|
+
creditsPerOutputToken?: number;
|
|
10
|
+
timeouts?: ProviderTimeouts;
|
|
11
|
+
};
|
|
12
|
+
export declare const mistral: (model: string, options?: MistralOptions) => Provider;
|