@openclaw/ai 0.0.0 → 2026.7.1-2
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 +27 -3
- package/dist/anthropic-B5gZQM5X.mjs +1383 -0
- package/dist/api-registry-BXYnCOIR.d.mts +33 -0
- package/dist/azure-openai-responses-DNoSk8Uy.mjs +141 -0
- package/dist/azure-openai-responses-client-compat-a_O_GVQV.mjs +41 -0
- package/dist/diagnostics-BaTA9eVl.d.mts +25 -0
- package/dist/diagnostics-COpOtRwq.mjs +36 -0
- package/dist/diagnostics.d.mts +2 -0
- package/dist/diagnostics.mjs +2 -0
- package/dist/env-api-keys-CtMlqaQ4.mjs +171 -0
- package/dist/event-stream-0nZeBKl2.d.mts +26 -0
- package/dist/event-stream-ReMmOTzX.mjs +65 -0
- package/dist/event-stream.d.mts +2 -0
- package/dist/event-stream.mjs +2 -0
- package/dist/github-copilot-headers-BsH5cqGj.mjs +48 -0
- package/dist/google-D6sIQ1bL.mjs +55 -0
- package/dist/google-shared-ZPSl2qTi.mjs +548 -0
- package/dist/google-vertex-rDGwkoZK.mjs +111 -0
- package/dist/hash-CHgqbJmD.mjs +16 -0
- package/dist/headers-B_e4-1J0.mjs +9 -0
- package/dist/host-4t713IeR.mjs +37 -0
- package/dist/index-BoTnz8cv.d.mts +74 -0
- package/dist/index.d.mts +69 -0
- package/dist/index.mjs +7 -0
- package/dist/internal/anthropic.d.mts +234 -0
- package/dist/internal/anthropic.mjs +4 -0
- package/dist/internal/openai.d.mts +244 -0
- package/dist/internal/openai.mjs +7 -0
- package/dist/internal/runtime.d.mts +245 -0
- package/dist/internal/runtime.mjs +176 -0
- package/dist/internal/shared.d.mts +48 -0
- package/dist/internal/shared.mjs +3 -0
- package/dist/json-parse-DzNSIQBq.mjs +134 -0
- package/dist/llm-request-activity-CehVkZP-.mjs +35 -0
- package/dist/mistral-CePVNdws.mjs +563 -0
- package/dist/model-utils-DgmOla96.mjs +69 -0
- package/dist/openai-chatgpt-jwt-DhAAzLkj.mjs +39 -0
- package/dist/openai-chatgpt-responses-DVC4Bk_A.mjs +1068 -0
- package/dist/openai-completions-B9QLIq2U.mjs +844 -0
- package/dist/openai-responses-B6LylGxM.mjs +136 -0
- package/dist/openai-responses-shared-sj2YUPYc.mjs +1944 -0
- package/dist/openai-tool-projection-BknoV11q.mjs +195 -0
- package/dist/providers.d.mts +11 -0
- package/dist/providers.mjs +109 -0
- package/dist/reasoning-tag-text-partitioner-axhAdUwg.mjs +394 -0
- package/dist/sanitize-unicode-BZiVbGwK.d.mts +24 -0
- package/dist/sanitize-unicode-DT5o51ur.mjs +26 -0
- package/dist/src-CZ503MYJ.mjs +99 -0
- package/dist/stream-CREqxHgU.mjs +74 -0
- package/dist/stream-first-event-timeout-RjWszj8c.mjs +106 -0
- package/dist/streaming-byte-guard-BrbkbwUu.mjs +46 -0
- package/dist/tool-schema-json-projection-BXtBc_mD.mjs +74 -0
- package/dist/transform-messages-BhGF_fF4.mjs +507 -0
- package/dist/types-BVVgDSdq.d.mts +1 -0
- package/dist/types-DRgdPqaZ.d.mts +587 -0
- package/dist/types.d.mts +6 -0
- package/dist/types.mjs +5 -0
- package/dist/validation-BDMWOr8d.d.mts +9 -0
- package/dist/validation-FrchoOlv.mjs +199 -0
- package/dist/validation.d.mts +2 -0
- package/dist/validation.mjs +2 -0
- package/npm-shrinkwrap.json +645 -0
- package/package.json +74 -2
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { n as getEnvApiKey } from "./env-api-keys-CtMlqaQ4.mjs";
|
|
2
|
+
import { t as AssistantMessageEventStream } from "./event-stream-ReMmOTzX.mjs";
|
|
3
|
+
import { n as getAiTransportHost } from "./host-4t713IeR.mjs";
|
|
4
|
+
import { n as calculateCost, r as clampThinkingLevel } from "./model-utils-DgmOla96.mjs";
|
|
5
|
+
import { n as parseStreamingJson } from "./json-parse-DzNSIQBq.mjs";
|
|
6
|
+
import { t as sanitizeSurrogates } from "./sanitize-unicode-DT5o51ur.mjs";
|
|
7
|
+
import { b as stripSystemPromptCacheBoundary, i as extractToolResultText, n as describeToolResultMediaPlaceholder, o as buildBaseOptions, t as transformMessages } from "./transform-messages-BhGF_fF4.mjs";
|
|
8
|
+
import { t as shortHash } from "./hash-CHgqbJmD.mjs";
|
|
9
|
+
import { t as createSseByteGuard } from "./streaming-byte-guard-BrbkbwUu.mjs";
|
|
10
|
+
import { HTTPClient, Mistral } from "@mistralai/mistralai";
|
|
11
|
+
//#region packages/ai/src/providers/mistral.ts
|
|
12
|
+
const MISTRAL_TOOL_CALL_ID_LENGTH = 9;
|
|
13
|
+
const MAX_MISTRAL_ERROR_BODY_CHARS = 4e3;
|
|
14
|
+
const MISTRAL_STREAM_BODY_MAX_BYTES = 16 * 1024 * 1024;
|
|
15
|
+
/**
|
|
16
|
+
* Builds a `Fetcher` that wraps the default `fetch` with a 16 MiB byte cap
|
|
17
|
+
* on streamed response bodies. The wrapped `Response.body` exposes a
|
|
18
|
+
* `ReadableStream` whose chunks flow through `createSseByteGuard`, so the
|
|
19
|
+
* SDK's internal SSE parser (`EventStream` in
|
|
20
|
+
* `@mistralai/mistralai/lib/event-streams.ts`) reads exactly as it would on
|
|
21
|
+
* an unbounded body — but bounded.
|
|
22
|
+
*
|
|
23
|
+
* Bodyless responses (no `body` or no `getReader`) are returned unchanged so
|
|
24
|
+
* the SDK's error-path `res.arrayBuffer()` call still works.
|
|
25
|
+
*/
|
|
26
|
+
function createBoundedMistralFetcher(maxBytes = MISTRAL_STREAM_BODY_MAX_BYTES, upstreamFetch = fetch) {
|
|
27
|
+
return async (input, init) => {
|
|
28
|
+
const response = init == null ? await upstreamFetch(input) : await upstreamFetch(input, init);
|
|
29
|
+
if (!response.body || typeof response.body.getReader !== "function") return response;
|
|
30
|
+
const guard = createSseByteGuard(response.body.getReader(), {
|
|
31
|
+
maxBytes,
|
|
32
|
+
onOverflow: ({ size, maxBytes: cap }) => /* @__PURE__ */ new Error(`mistral: stream body exceeds ${cap} bytes (got ${size})`)
|
|
33
|
+
});
|
|
34
|
+
const guardedStream = new ReadableStream({
|
|
35
|
+
async pull(controller) {
|
|
36
|
+
const { done, value } = await guard.read();
|
|
37
|
+
if (done) {
|
|
38
|
+
controller.close();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
controller.enqueue(value);
|
|
42
|
+
},
|
|
43
|
+
async cancel(reason) {
|
|
44
|
+
await guard.cancel(reason);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return new Response(guardedStream, {
|
|
48
|
+
status: response.status,
|
|
49
|
+
statusText: response.statusText,
|
|
50
|
+
headers: response.headers
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Stream responses from Mistral using `chat.stream`.
|
|
56
|
+
*/
|
|
57
|
+
const streamMistral = (model, context, options) => {
|
|
58
|
+
const stream = new AssistantMessageEventStream();
|
|
59
|
+
(async () => {
|
|
60
|
+
const output = createOutput(model);
|
|
61
|
+
try {
|
|
62
|
+
const apiKey = options?.apiKey || getEnvApiKey(model.provider);
|
|
63
|
+
if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);
|
|
64
|
+
const mistral = new Mistral({
|
|
65
|
+
apiKey,
|
|
66
|
+
serverURL: model.baseUrl,
|
|
67
|
+
httpClient: new HTTPClient({ fetcher: createBoundedMistralFetcher(MISTRAL_STREAM_BODY_MAX_BYTES, getAiTransportHost().buildModelFetch(model) ?? fetch) })
|
|
68
|
+
});
|
|
69
|
+
const normalizeMistralToolCallId = createMistralToolCallIdNormalizer();
|
|
70
|
+
let payload = buildChatPayload(model, context, transformMessages(context.messages, model, (id) => normalizeMistralToolCallId(id)), options);
|
|
71
|
+
const nextPayload = await options?.onPayload?.(payload, model);
|
|
72
|
+
if (nextPayload !== void 0) payload = nextPayload;
|
|
73
|
+
const mistralStream = await mistral.chat.stream(payload, buildRequestOptions(model, options));
|
|
74
|
+
stream.push({
|
|
75
|
+
type: "start",
|
|
76
|
+
partial: output
|
|
77
|
+
});
|
|
78
|
+
await consumeChatStream(model, output, stream, mistralStream);
|
|
79
|
+
if (options?.signal?.aborted) throw new Error("Request was aborted");
|
|
80
|
+
if (output.stopReason === "aborted" || output.stopReason === "error") throw new Error("An unknown error occurred");
|
|
81
|
+
stream.push({
|
|
82
|
+
type: "done",
|
|
83
|
+
reason: output.stopReason,
|
|
84
|
+
message: output
|
|
85
|
+
});
|
|
86
|
+
stream.end();
|
|
87
|
+
} catch (error) {
|
|
88
|
+
for (const block of output.content) delete block.partialArgs;
|
|
89
|
+
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
90
|
+
output.errorMessage = formatMistralError(error);
|
|
91
|
+
stream.push({
|
|
92
|
+
type: "error",
|
|
93
|
+
reason: output.stopReason,
|
|
94
|
+
error: output
|
|
95
|
+
});
|
|
96
|
+
stream.end();
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
return stream;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Maps provider-agnostic `SimpleStreamOptions` to Mistral options.
|
|
103
|
+
*/
|
|
104
|
+
const streamSimpleMistral = (model, context, options) => {
|
|
105
|
+
const apiKey = options?.apiKey || getEnvApiKey(model.provider);
|
|
106
|
+
if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);
|
|
107
|
+
const base = buildBaseOptions(model, options, apiKey);
|
|
108
|
+
const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : void 0;
|
|
109
|
+
const reasoning = clampedReasoning === "off" ? void 0 : clampedReasoning;
|
|
110
|
+
const shouldUseReasoning = model.reasoning && reasoning !== void 0;
|
|
111
|
+
return streamMistral(model, context, {
|
|
112
|
+
...base,
|
|
113
|
+
promptMode: shouldUseReasoning && usesPromptModeReasoning(model) ? "reasoning" : void 0,
|
|
114
|
+
reasoningEffort: shouldUseReasoning && usesReasoningEffort(model) ? mapReasoningEffort(model, reasoning) : void 0
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
function createOutput(model) {
|
|
118
|
+
return {
|
|
119
|
+
role: "assistant",
|
|
120
|
+
content: [],
|
|
121
|
+
api: model.api,
|
|
122
|
+
provider: model.provider,
|
|
123
|
+
model: model.id,
|
|
124
|
+
usage: {
|
|
125
|
+
input: 0,
|
|
126
|
+
output: 0,
|
|
127
|
+
cacheRead: 0,
|
|
128
|
+
cacheWrite: 0,
|
|
129
|
+
totalTokens: 0,
|
|
130
|
+
cost: {
|
|
131
|
+
input: 0,
|
|
132
|
+
output: 0,
|
|
133
|
+
cacheRead: 0,
|
|
134
|
+
cacheWrite: 0,
|
|
135
|
+
total: 0
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
stopReason: "stop",
|
|
139
|
+
timestamp: Date.now()
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function createMistralToolCallIdNormalizer() {
|
|
143
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
144
|
+
const reverseMap = /* @__PURE__ */ new Map();
|
|
145
|
+
return (id) => {
|
|
146
|
+
const existing = idMap.get(id);
|
|
147
|
+
if (existing) return existing;
|
|
148
|
+
let attempt = 0;
|
|
149
|
+
while (true) {
|
|
150
|
+
const candidate = deriveMistralToolCallId(id, attempt);
|
|
151
|
+
const owner = reverseMap.get(candidate);
|
|
152
|
+
if (!owner || owner === id) {
|
|
153
|
+
idMap.set(id, candidate);
|
|
154
|
+
reverseMap.set(candidate, id);
|
|
155
|
+
return candidate;
|
|
156
|
+
}
|
|
157
|
+
attempt++;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function deriveMistralToolCallId(id, attempt) {
|
|
162
|
+
const normalized = id.replace(/[^a-zA-Z0-9]/g, "");
|
|
163
|
+
if (attempt === 0 && normalized.length === MISTRAL_TOOL_CALL_ID_LENGTH) return normalized;
|
|
164
|
+
const seedBase = normalized || id;
|
|
165
|
+
return shortHash(attempt === 0 ? seedBase : `${seedBase}:${attempt}`).replace(/[^a-zA-Z0-9]/g, "").slice(0, MISTRAL_TOOL_CALL_ID_LENGTH);
|
|
166
|
+
}
|
|
167
|
+
function formatMistralError(error) {
|
|
168
|
+
if (error instanceof Error) {
|
|
169
|
+
const sdkError = error;
|
|
170
|
+
const statusCode = typeof sdkError.statusCode === "number" ? sdkError.statusCode : void 0;
|
|
171
|
+
const bodyText = typeof sdkError.body === "string" ? sdkError.body.trim() : void 0;
|
|
172
|
+
if (statusCode !== void 0 && bodyText) return `Mistral API error (${statusCode}): ${truncateErrorText(bodyText, MAX_MISTRAL_ERROR_BODY_CHARS)}`;
|
|
173
|
+
if (statusCode !== void 0) return `Mistral API error (${statusCode}): ${error.message}`;
|
|
174
|
+
return error.message;
|
|
175
|
+
}
|
|
176
|
+
return safeJsonStringify(error);
|
|
177
|
+
}
|
|
178
|
+
function truncateErrorText(text, maxChars) {
|
|
179
|
+
if (text.length <= maxChars) return text;
|
|
180
|
+
return `${text.slice(0, maxChars)}... [truncated ${text.length - maxChars} chars]`;
|
|
181
|
+
}
|
|
182
|
+
function safeJsonStringify(value) {
|
|
183
|
+
try {
|
|
184
|
+
const serialized = JSON.stringify(value);
|
|
185
|
+
return serialized === void 0 ? String(value) : serialized;
|
|
186
|
+
} catch {
|
|
187
|
+
return String(value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function buildRequestOptions(model, options) {
|
|
191
|
+
const requestOptions = { retries: { strategy: "none" } };
|
|
192
|
+
if (options?.signal) requestOptions.signal = options.signal;
|
|
193
|
+
const headers = {};
|
|
194
|
+
if (model.headers) Object.assign(headers, model.headers);
|
|
195
|
+
if (options?.headers) Object.assign(headers, options.headers);
|
|
196
|
+
if (options?.sessionId && !headers["x-affinity"]) headers["x-affinity"] = options.sessionId;
|
|
197
|
+
if (Object.keys(headers).length > 0) requestOptions.headers = headers;
|
|
198
|
+
return requestOptions;
|
|
199
|
+
}
|
|
200
|
+
function buildChatPayload(model, context, messages, options) {
|
|
201
|
+
const payload = {
|
|
202
|
+
model: model.id,
|
|
203
|
+
stream: true,
|
|
204
|
+
messages: toChatMessages(messages, model.input.includes("image"))
|
|
205
|
+
};
|
|
206
|
+
let convertedToolNames;
|
|
207
|
+
if (context.tools?.length) {
|
|
208
|
+
const tools = toFunctionTools(context.tools);
|
|
209
|
+
convertedToolNames = new Set(tools.map((tool) => tool.function.name));
|
|
210
|
+
if (tools.length > 0) payload.tools = tools;
|
|
211
|
+
}
|
|
212
|
+
if (options?.temperature !== void 0) payload.temperature = options.temperature;
|
|
213
|
+
if (options?.maxTokens !== void 0) payload.maxTokens = options.maxTokens;
|
|
214
|
+
if (options?.stop !== void 0 && options.stop.length > 0) payload.stop = options.stop;
|
|
215
|
+
if (options?.toolChoice) {
|
|
216
|
+
const toolChoice = mapToolChoice(options.toolChoice, convertedToolNames);
|
|
217
|
+
if (toolChoice) payload.toolChoice = toolChoice;
|
|
218
|
+
}
|
|
219
|
+
if (options?.promptMode) payload.promptMode = options.promptMode;
|
|
220
|
+
if (options?.reasoningEffort) payload.reasoningEffort = options.reasoningEffort;
|
|
221
|
+
if (context.systemPrompt) payload.messages.unshift({
|
|
222
|
+
role: "system",
|
|
223
|
+
content: sanitizeSurrogates(stripSystemPromptCacheBoundary(context.systemPrompt))
|
|
224
|
+
});
|
|
225
|
+
return payload;
|
|
226
|
+
}
|
|
227
|
+
async function consumeChatStream(model, output, stream, mistralStream) {
|
|
228
|
+
let currentBlock = null;
|
|
229
|
+
const blocks = output.content;
|
|
230
|
+
const blockIndex = () => blocks.length - 1;
|
|
231
|
+
const toolBlocksByKey = /* @__PURE__ */ new Map();
|
|
232
|
+
const finishCurrentBlock = (block) => {
|
|
233
|
+
if (!block) return;
|
|
234
|
+
if (block.type === "text") {
|
|
235
|
+
stream.push({
|
|
236
|
+
type: "text_end",
|
|
237
|
+
contentIndex: blockIndex(),
|
|
238
|
+
content: block.text,
|
|
239
|
+
partial: output
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (block.type === "thinking") stream.push({
|
|
244
|
+
type: "thinking_end",
|
|
245
|
+
contentIndex: blockIndex(),
|
|
246
|
+
content: block.thinking,
|
|
247
|
+
partial: output
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
for await (const event of mistralStream) {
|
|
251
|
+
const chunk = event.data;
|
|
252
|
+
output.responseId ||= chunk.id;
|
|
253
|
+
if (chunk.usage) {
|
|
254
|
+
output.usage.input = chunk.usage.promptTokens || 0;
|
|
255
|
+
output.usage.output = chunk.usage.completionTokens || 0;
|
|
256
|
+
output.usage.cacheRead = 0;
|
|
257
|
+
output.usage.cacheWrite = 0;
|
|
258
|
+
output.usage.totalTokens = chunk.usage.totalTokens || output.usage.input + output.usage.output;
|
|
259
|
+
calculateCost(model, output.usage);
|
|
260
|
+
}
|
|
261
|
+
const choice = chunk.choices[0];
|
|
262
|
+
if (!choice) continue;
|
|
263
|
+
if (choice.finishReason) output.stopReason = mapChatStopReason(choice.finishReason);
|
|
264
|
+
const delta = choice.delta;
|
|
265
|
+
if (delta.content !== null && delta.content !== void 0) {
|
|
266
|
+
const contentItems = typeof delta.content === "string" ? [delta.content] : delta.content;
|
|
267
|
+
for (const item of contentItems) {
|
|
268
|
+
if (typeof item === "string") {
|
|
269
|
+
const textDelta = sanitizeSurrogates(item);
|
|
270
|
+
if (!currentBlock || currentBlock.type !== "text") {
|
|
271
|
+
finishCurrentBlock(currentBlock);
|
|
272
|
+
currentBlock = {
|
|
273
|
+
type: "text",
|
|
274
|
+
text: ""
|
|
275
|
+
};
|
|
276
|
+
output.content.push(currentBlock);
|
|
277
|
+
stream.push({
|
|
278
|
+
type: "text_start",
|
|
279
|
+
contentIndex: blockIndex(),
|
|
280
|
+
partial: output
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
currentBlock.text += textDelta;
|
|
284
|
+
stream.push({
|
|
285
|
+
type: "text_delta",
|
|
286
|
+
contentIndex: blockIndex(),
|
|
287
|
+
delta: textDelta,
|
|
288
|
+
partial: output
|
|
289
|
+
});
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (item.type === "thinking") {
|
|
293
|
+
const thinkingDelta = sanitizeSurrogates(item.thinking.map((part) => "text" in part ? part.text : "").filter((text) => text.length > 0).join(""));
|
|
294
|
+
if (!thinkingDelta) continue;
|
|
295
|
+
if (!currentBlock || currentBlock.type !== "thinking") {
|
|
296
|
+
finishCurrentBlock(currentBlock);
|
|
297
|
+
currentBlock = {
|
|
298
|
+
type: "thinking",
|
|
299
|
+
thinking: ""
|
|
300
|
+
};
|
|
301
|
+
output.content.push(currentBlock);
|
|
302
|
+
stream.push({
|
|
303
|
+
type: "thinking_start",
|
|
304
|
+
contentIndex: blockIndex(),
|
|
305
|
+
partial: output
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
currentBlock.thinking += thinkingDelta;
|
|
309
|
+
stream.push({
|
|
310
|
+
type: "thinking_delta",
|
|
311
|
+
contentIndex: blockIndex(),
|
|
312
|
+
delta: thinkingDelta,
|
|
313
|
+
partial: output
|
|
314
|
+
});
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (item.type === "text") {
|
|
318
|
+
const textDelta = sanitizeSurrogates(item.text);
|
|
319
|
+
if (!currentBlock || currentBlock.type !== "text") {
|
|
320
|
+
finishCurrentBlock(currentBlock);
|
|
321
|
+
currentBlock = {
|
|
322
|
+
type: "text",
|
|
323
|
+
text: ""
|
|
324
|
+
};
|
|
325
|
+
output.content.push(currentBlock);
|
|
326
|
+
stream.push({
|
|
327
|
+
type: "text_start",
|
|
328
|
+
contentIndex: blockIndex(),
|
|
329
|
+
partial: output
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
currentBlock.text += textDelta;
|
|
333
|
+
stream.push({
|
|
334
|
+
type: "text_delta",
|
|
335
|
+
contentIndex: blockIndex(),
|
|
336
|
+
delta: textDelta,
|
|
337
|
+
partial: output
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const toolCalls = delta.toolCalls || [];
|
|
343
|
+
for (const toolCall of toolCalls) {
|
|
344
|
+
if (currentBlock) {
|
|
345
|
+
finishCurrentBlock(currentBlock);
|
|
346
|
+
currentBlock = null;
|
|
347
|
+
}
|
|
348
|
+
const callId = toolCall.id && toolCall.id !== "null" ? toolCall.id : deriveMistralToolCallId(`toolcall:${toolCall.index ?? 0}`, 0);
|
|
349
|
+
const key = `${callId}:${toolCall.index || 0}`;
|
|
350
|
+
const existingIndex = toolBlocksByKey.get(key);
|
|
351
|
+
let block;
|
|
352
|
+
if (existingIndex !== void 0) {
|
|
353
|
+
const existing = output.content[existingIndex];
|
|
354
|
+
if (existing?.type === "toolCall") block = existing;
|
|
355
|
+
}
|
|
356
|
+
if (!block) {
|
|
357
|
+
block = {
|
|
358
|
+
type: "toolCall",
|
|
359
|
+
id: callId,
|
|
360
|
+
name: toolCall.function.name,
|
|
361
|
+
arguments: {},
|
|
362
|
+
partialArgs: ""
|
|
363
|
+
};
|
|
364
|
+
output.content.push(block);
|
|
365
|
+
toolBlocksByKey.set(key, output.content.length - 1);
|
|
366
|
+
stream.push({
|
|
367
|
+
type: "toolcall_start",
|
|
368
|
+
contentIndex: output.content.length - 1,
|
|
369
|
+
partial: output
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const argsDelta = typeof toolCall.function.arguments === "string" ? toolCall.function.arguments : JSON.stringify(toolCall.function.arguments || {});
|
|
373
|
+
block.partialArgs = (block.partialArgs || "") + argsDelta;
|
|
374
|
+
block.arguments = parseStreamingJson(block.partialArgs);
|
|
375
|
+
stream.push({
|
|
376
|
+
type: "toolcall_delta",
|
|
377
|
+
contentIndex: toolBlocksByKey.get(key),
|
|
378
|
+
delta: argsDelta,
|
|
379
|
+
partial: output
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
finishCurrentBlock(currentBlock);
|
|
384
|
+
for (const index of toolBlocksByKey.values()) {
|
|
385
|
+
const block = output.content[index];
|
|
386
|
+
if (block.type !== "toolCall") continue;
|
|
387
|
+
const toolBlock = block;
|
|
388
|
+
toolBlock.arguments = parseStreamingJson(toolBlock.partialArgs);
|
|
389
|
+
delete toolBlock.partialArgs;
|
|
390
|
+
stream.push({
|
|
391
|
+
type: "toolcall_end",
|
|
392
|
+
contentIndex: index,
|
|
393
|
+
toolCall: toolBlock,
|
|
394
|
+
partial: output
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function toFunctionTools(tools) {
|
|
399
|
+
return tools.flatMap((tool) => {
|
|
400
|
+
try {
|
|
401
|
+
return {
|
|
402
|
+
type: "function",
|
|
403
|
+
function: {
|
|
404
|
+
name: tool.name,
|
|
405
|
+
description: tool.description,
|
|
406
|
+
parameters: stripSymbolKeys(tool.parameters),
|
|
407
|
+
strict: false
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
} catch {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
function stripSymbolKeys(value) {
|
|
416
|
+
if (Array.isArray(value)) return value.map((item) => stripSymbolKeys(item));
|
|
417
|
+
if (value && typeof value === "object") {
|
|
418
|
+
const result = {};
|
|
419
|
+
for (const [key, entry] of Object.entries(value)) result[key] = stripSymbolKeys(entry);
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
return value;
|
|
423
|
+
}
|
|
424
|
+
function toChatMessages(messages, supportsImages) {
|
|
425
|
+
const result = [];
|
|
426
|
+
for (const msg of messages) {
|
|
427
|
+
if (msg.role === "user") {
|
|
428
|
+
if (typeof msg.content === "string") {
|
|
429
|
+
result.push({
|
|
430
|
+
role: "user",
|
|
431
|
+
content: sanitizeSurrogates(msg.content)
|
|
432
|
+
});
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const hadImages = msg.content.some((item) => item.type === "image");
|
|
436
|
+
const content = msg.content.filter((item) => item.type === "text" || supportsImages).map((item) => {
|
|
437
|
+
if (item.type === "text") return {
|
|
438
|
+
type: "text",
|
|
439
|
+
text: sanitizeSurrogates(item.text)
|
|
440
|
+
};
|
|
441
|
+
return {
|
|
442
|
+
type: "image_url",
|
|
443
|
+
imageUrl: `data:${item.mimeType};base64,${item.data}`
|
|
444
|
+
};
|
|
445
|
+
});
|
|
446
|
+
if (content.length > 0) {
|
|
447
|
+
result.push({
|
|
448
|
+
role: "user",
|
|
449
|
+
content
|
|
450
|
+
});
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (hadImages && !supportsImages) result.push({
|
|
454
|
+
role: "user",
|
|
455
|
+
content: "(image omitted: model does not support images)"
|
|
456
|
+
});
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (msg.role === "assistant") {
|
|
460
|
+
const contentParts = [];
|
|
461
|
+
const toolCalls = [];
|
|
462
|
+
for (const block of msg.content) {
|
|
463
|
+
if (block.type === "text") {
|
|
464
|
+
if (block.text.trim().length > 0) contentParts.push({
|
|
465
|
+
type: "text",
|
|
466
|
+
text: sanitizeSurrogates(block.text)
|
|
467
|
+
});
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (block.type === "thinking") {
|
|
471
|
+
if (block.thinking.trim().length > 0) contentParts.push({
|
|
472
|
+
type: "thinking",
|
|
473
|
+
thinking: [{
|
|
474
|
+
type: "text",
|
|
475
|
+
text: sanitizeSurrogates(block.thinking)
|
|
476
|
+
}]
|
|
477
|
+
});
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
toolCalls.push({
|
|
481
|
+
id: block.id,
|
|
482
|
+
type: "function",
|
|
483
|
+
function: {
|
|
484
|
+
name: block.name,
|
|
485
|
+
arguments: JSON.stringify(block.arguments || {})
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
const assistantMessage = { role: "assistant" };
|
|
490
|
+
if (contentParts.length > 0) assistantMessage.content = contentParts;
|
|
491
|
+
if (toolCalls.length > 0) assistantMessage.toolCalls = toolCalls;
|
|
492
|
+
if (contentParts.length > 0 || toolCalls.length > 0) result.push(assistantMessage);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const toolContent = [];
|
|
496
|
+
const toolText = buildToolResultText(extractToolResultText(msg.content), describeToolResultMediaPlaceholder(msg.content), msg.content.some((part) => part.type === "image"), supportsImages, msg.isError);
|
|
497
|
+
toolContent.push({
|
|
498
|
+
type: "text",
|
|
499
|
+
text: toolText
|
|
500
|
+
});
|
|
501
|
+
for (const part of msg.content) {
|
|
502
|
+
if (!supportsImages) continue;
|
|
503
|
+
if (part.type !== "image") continue;
|
|
504
|
+
toolContent.push({
|
|
505
|
+
type: "image_url",
|
|
506
|
+
imageUrl: `data:${part.mimeType};base64,${part.data}`
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
result.push({
|
|
510
|
+
role: "tool",
|
|
511
|
+
toolCallId: msg.toolCallId,
|
|
512
|
+
name: msg.toolName,
|
|
513
|
+
content: toolContent
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
function buildToolResultText(text, mediaPlaceholder, hasImages, supportsImages, isError) {
|
|
519
|
+
const trimmed = text.trim();
|
|
520
|
+
const errorPrefix = isError ? "[tool error] " : "";
|
|
521
|
+
if (trimmed.length > 0) return `${errorPrefix}${trimmed}${hasImages && !supportsImages ? "\n[tool image omitted: model does not support images]" : ""}`;
|
|
522
|
+
if (mediaPlaceholder) {
|
|
523
|
+
if (!hasImages || supportsImages) return `${errorPrefix}${mediaPlaceholder}`;
|
|
524
|
+
return `${errorPrefix}${mediaPlaceholder === "(see attached media)" ? "(media omitted: model does not support images)" : "(image omitted: model does not support images)"}`;
|
|
525
|
+
}
|
|
526
|
+
return isError ? "[tool error] (no tool output)" : "(no tool output)";
|
|
527
|
+
}
|
|
528
|
+
function usesReasoningEffort(model) {
|
|
529
|
+
return model.id === "mistral-small-2603" || model.id === "mistral-small-latest" || model.id === "mistral-medium-3-5";
|
|
530
|
+
}
|
|
531
|
+
function usesPromptModeReasoning(model) {
|
|
532
|
+
return model.reasoning && !usesReasoningEffort(model);
|
|
533
|
+
}
|
|
534
|
+
function mapReasoningEffort(model, level) {
|
|
535
|
+
return model.thinkingLevelMap?.[level] ?? "high";
|
|
536
|
+
}
|
|
537
|
+
function mapToolChoice(choice, convertedToolNames) {
|
|
538
|
+
if (!choice) return;
|
|
539
|
+
if (convertedToolNames && convertedToolNames.size === 0) {
|
|
540
|
+
if (choice === "none" || choice === "auto") return choice === "none" ? "none" : void 0;
|
|
541
|
+
throw new Error("Mistral tool_choice requires a tool, but no tools survived schema conversion");
|
|
542
|
+
}
|
|
543
|
+
if (choice === "auto" || choice === "none" || choice === "any" || choice === "required") return choice;
|
|
544
|
+
const toolName = choice.function.name;
|
|
545
|
+
if (convertedToolNames && !convertedToolNames.has(toolName)) throw new Error(`Mistral tool_choice requested unavailable tool "${toolName}" after schema conversion`);
|
|
546
|
+
return {
|
|
547
|
+
type: "function",
|
|
548
|
+
function: { name: toolName }
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function mapChatStopReason(reason) {
|
|
552
|
+
if (reason === null) return "stop";
|
|
553
|
+
switch (reason) {
|
|
554
|
+
case "stop": return "stop";
|
|
555
|
+
case "length":
|
|
556
|
+
case "model_length": return "length";
|
|
557
|
+
case "tool_calls": return "toolUse";
|
|
558
|
+
case "error": return "error";
|
|
559
|
+
default: return "stop";
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
//#endregion
|
|
563
|
+
export { createBoundedMistralFetcher, streamMistral, streamSimpleMistral };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { c as resolveClaudeNativeThinkingLevelMap, i as requiresClaudeMandatoryAdaptiveThinking } from "./src-CZ503MYJ.mjs";
|
|
2
|
+
//#region packages/ai/src/model-utils.ts
|
|
3
|
+
/** Calculates and stores model cost fields from token usage and per-million pricing. */
|
|
4
|
+
function calculateCost(model, usage) {
|
|
5
|
+
usage.cost.input = model.cost.input / 1e6 * usage.input;
|
|
6
|
+
usage.cost.output = model.cost.output / 1e6 * usage.output;
|
|
7
|
+
usage.cost.cacheRead = model.cost.cacheRead / 1e6 * usage.cacheRead;
|
|
8
|
+
usage.cost.cacheWrite = model.cost.cacheWrite / 1e6 * usage.cacheWrite;
|
|
9
|
+
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
|
|
10
|
+
return usage.cost;
|
|
11
|
+
}
|
|
12
|
+
/** Replaces the catalog estimate when the provider reports an authoritative billed total. */
|
|
13
|
+
function applyProviderReportedUsageCost(usage, reportedCost) {
|
|
14
|
+
if (typeof reportedCost !== "number" || !Number.isFinite(reportedCost) || reportedCost < 0) return;
|
|
15
|
+
usage.cost.total = reportedCost;
|
|
16
|
+
usage.cost.totalOrigin = "provider-billed";
|
|
17
|
+
}
|
|
18
|
+
const EXTENDED_THINKING_LEVELS = [
|
|
19
|
+
"off",
|
|
20
|
+
"minimal",
|
|
21
|
+
"low",
|
|
22
|
+
"medium",
|
|
23
|
+
"high",
|
|
24
|
+
"xhigh",
|
|
25
|
+
"max"
|
|
26
|
+
];
|
|
27
|
+
function resolveThinkingLevelMap(model) {
|
|
28
|
+
return model.api === "anthropic-messages" ? resolveClaudeNativeThinkingLevelMap(model) ?? model.thinkingLevelMap : model.thinkingLevelMap;
|
|
29
|
+
}
|
|
30
|
+
/** Returns thinking levels exposed by a reasoning-capable model. */
|
|
31
|
+
function getSupportedThinkingLevels(model) {
|
|
32
|
+
const mandatoryAdaptiveContract = model.api === "anthropic-messages" && requiresClaudeMandatoryAdaptiveThinking(model);
|
|
33
|
+
if (!model.reasoning && !mandatoryAdaptiveContract) return ["off"];
|
|
34
|
+
const thinkingLevelMap = resolveThinkingLevelMap(model);
|
|
35
|
+
return EXTENDED_THINKING_LEVELS.filter((level) => {
|
|
36
|
+
const mapped = thinkingLevelMap?.[level];
|
|
37
|
+
if (mapped === null) return false;
|
|
38
|
+
if (level === "xhigh" || level === "max") return mapped !== void 0;
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** Clamps a requested thinking level to the closest supported level for a model. */
|
|
43
|
+
function clampThinkingLevel(model, level) {
|
|
44
|
+
const availableLevels = getSupportedThinkingLevels(model);
|
|
45
|
+
if (availableLevels.includes(level)) return level;
|
|
46
|
+
const requestedIndex = EXTENDED_THINKING_LEVELS.indexOf(level);
|
|
47
|
+
if (requestedIndex === -1) return availableLevels[0] ?? "off";
|
|
48
|
+
const thinkingLevelMap = resolveThinkingLevelMap(model);
|
|
49
|
+
if ((level === "xhigh" || level === "max") && thinkingLevelMap?.[level] === null) for (let i = requestedIndex - 1; i >= 0; i--) {
|
|
50
|
+
const candidate = EXTENDED_THINKING_LEVELS[i];
|
|
51
|
+
if (availableLevels.includes(candidate)) return candidate;
|
|
52
|
+
}
|
|
53
|
+
for (let i = requestedIndex; i < EXTENDED_THINKING_LEVELS.length; i++) {
|
|
54
|
+
const candidate = EXTENDED_THINKING_LEVELS[i];
|
|
55
|
+
if (availableLevels.includes(candidate)) return candidate;
|
|
56
|
+
}
|
|
57
|
+
for (let i = requestedIndex - 1; i >= 0; i--) {
|
|
58
|
+
const candidate = EXTENDED_THINKING_LEVELS[i];
|
|
59
|
+
if (availableLevels.includes(candidate)) return candidate;
|
|
60
|
+
}
|
|
61
|
+
return availableLevels[0] ?? "off";
|
|
62
|
+
}
|
|
63
|
+
/** Compares model identity by provider and id. */
|
|
64
|
+
function modelsAreEqual(a, b) {
|
|
65
|
+
if (!a || !b) return false;
|
|
66
|
+
return a.id === b.id && a.provider === b.provider;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { modelsAreEqual as a, getSupportedThinkingLevels as i, calculateCost as n, clampThinkingLevel as r, applyProviderReportedUsageCost as t };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region packages/ai/src/session-resources.ts
|
|
2
|
+
const sessionResourceCleanups = /* @__PURE__ */ new Set();
|
|
3
|
+
/** Registers a session-resource cleanup hook and returns an unregister function. */
|
|
4
|
+
function registerSessionResourceCleanup(cleanup) {
|
|
5
|
+
sessionResourceCleanups.add(cleanup);
|
|
6
|
+
return () => {
|
|
7
|
+
sessionResourceCleanups.delete(cleanup);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** Runs all registered cleanup hooks, aggregating failures after every hook has run. */
|
|
11
|
+
function cleanupSessionResources(sessionId) {
|
|
12
|
+
const errors = [];
|
|
13
|
+
for (const cleanup of sessionResourceCleanups) try {
|
|
14
|
+
cleanup(sessionId);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
errors.push(error);
|
|
17
|
+
}
|
|
18
|
+
if (errors.length > 0) throw new AggregateError(errors, "Failed to cleanup session resources");
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region packages/ai/src/utils/oauth/openai-chatgpt-jwt.ts
|
|
22
|
+
const OPENAI_CODEX_AUTH_CLAIM = "https://api.openai.com/auth";
|
|
23
|
+
function decodeOpenAICodexJwtPayload(token) {
|
|
24
|
+
const parts = token.split(".");
|
|
25
|
+
if (parts.length !== 3) return null;
|
|
26
|
+
try {
|
|
27
|
+
const decoded = Buffer.from(parts[1] ?? "", "base64url").toString("utf8");
|
|
28
|
+
const parsed = JSON.parse(decoded);
|
|
29
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function resolveOpenAICodexAccountId(token) {
|
|
35
|
+
const accountId = decodeOpenAICodexJwtPayload(token)?.[OPENAI_CODEX_AUTH_CLAIM]?.chatgpt_account_id;
|
|
36
|
+
return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { registerSessionResourceCleanup as i, resolveOpenAICodexAccountId as n, cleanupSessionResources as r, decodeOpenAICodexJwtPayload as t };
|