@fleetagent/pi-ai 0.1.8 → 0.2.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +29 -24
- package/dist/cli.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +1 -1
- package/dist/env-api-keys.js.map +1 -1
- package/dist/image-models.generated.d.ts +225 -0
- package/dist/image-models.generated.d.ts.map +1 -1
- package/dist/image-models.generated.js +227 -2
- package/dist/image-models.generated.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +37 -2
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +6120 -2593
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +5347 -2110
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +259 -9
- package/dist/models.js.map +1 -1
- package/dist/oauth.d.ts +6 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +4 -1
- package/dist/oauth.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts +5 -4
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +303 -330
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts +5 -4
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +492 -499
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/azure-openai-responses.d.ts +4 -2
- package/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.js +52 -35
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/faux.d.ts +19 -22
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +106 -70
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/github-copilot-headers.d.ts +7 -4
- package/dist/providers/github-copilot-headers.d.ts.map +1 -1
- package/dist/providers/github-copilot-headers.js.map +1 -1
- package/dist/providers/google-shared.d.ts +24 -5
- package/dist/providers/google-shared.d.ts.map +1 -1
- package/dist/providers/google-shared.js +139 -140
- package/dist/providers/google-shared.js.map +1 -1
- package/dist/providers/google-vertex.d.ts +3 -7
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +216 -219
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/google.d.ts +4 -7
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +214 -218
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/images/openrouter.d.ts.map +1 -1
- package/dist/providers/images/openrouter.js +37 -27
- package/dist/providers/images/openrouter.js.map +1 -1
- package/dist/providers/mistral.d.ts +7 -6
- package/dist/providers/mistral.d.ts.map +1 -1
- package/dist/providers/mistral.js +287 -265
- package/dist/providers/mistral.js.map +1 -1
- package/dist/providers/openai-codex-responses.d.ts +8 -5
- package/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/dist/providers/openai-codex-responses.js +493 -391
- package/dist/providers/openai-codex-responses.js.map +1 -1
- package/dist/providers/openai-completions.d.ts +7 -10
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +670 -664
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-responses-shared.d.ts +5 -4
- package/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/dist/providers/openai-responses-shared.js +390 -360
- package/dist/providers/openai-responses-shared.js.map +1 -1
- package/dist/providers/openai-responses.d.ts +5 -5
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +134 -109
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/simple-options.d.ts +4 -2
- package/dist/providers/simple-options.d.ts.map +1 -1
- package/dist/providers/simple-options.js.map +1 -1
- package/dist/providers/transform-messages.d.ts.map +1 -1
- package/dist/providers/transform-messages.js +112 -137
- package/dist/providers/transform-messages.js.map +1 -1
- package/dist/stream.d.ts +0 -1
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +0 -1
- package/dist/stream.js.map +1 -1
- package/dist/types.d.ts +91 -63
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/error-body.d.ts.map +1 -1
- package/dist/utils/error-body.js +15 -4
- package/dist/utils/error-body.js.map +1 -1
- package/dist/utils/estimate.d.ts +4 -0
- package/dist/utils/estimate.d.ts.map +1 -1
- package/dist/utils/estimate.js +3 -1
- package/dist/utils/estimate.js.map +1 -1
- package/dist/utils/headers.d.ts +3 -0
- package/dist/utils/headers.d.ts.map +1 -1
- package/dist/utils/headers.js +21 -0
- package/dist/utils/headers.js.map +1 -1
- package/dist/utils/json-parse.d.ts.map +1 -1
- package/dist/utils/json-parse.js +17 -19
- package/dist/utils/json-parse.js.map +1 -1
- package/dist/utils/oauth/anthropic.d.ts +4 -11
- package/dist/utils/oauth/anthropic.d.ts.map +1 -1
- package/dist/utils/oauth/anthropic.js +100 -117
- package/dist/utils/oauth/anthropic.js.map +1 -1
- package/dist/utils/oauth/device-code.d.ts.map +1 -1
- package/dist/utils/oauth/device-code.js +51 -38
- package/dist/utils/oauth/device-code.js.map +1 -1
- package/dist/utils/oauth/github-copilot.d.ts +6 -9
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +47 -42
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +8 -17
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +27 -23
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/oauth-page.d.ts.map +1 -1
- package/dist/utils/oauth/oauth-page.js.map +1 -1
- package/dist/utils/oauth/openai-codex.d.ts +7 -12
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/dist/utils/oauth/openai-codex.js +72 -89
- package/dist/utils/oauth/openai-codex.js.map +1 -1
- package/dist/utils/oauth/pkce.d.ts +5 -4
- package/dist/utils/oauth/pkce.d.ts.map +1 -1
- package/dist/utils/oauth/pkce.js.map +1 -1
- package/dist/utils/oauth/types.d.ts +7 -2
- package/dist/utils/oauth/types.d.ts.map +1 -1
- package/dist/utils/oauth/types.js.map +1 -1
- package/dist/utils/provider-retry.d.ts +16 -0
- package/dist/utils/provider-retry.d.ts.map +1 -0
- package/dist/utils/provider-retry.js +94 -0
- package/dist/utils/provider-retry.js.map +1 -0
- package/dist/utils/retry.d.ts +14 -0
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +84 -0
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/typebox-helpers.d.ts +5 -4
- package/dist/utils/typebox-helpers.d.ts.map +1 -1
- package/dist/utils/typebox-helpers.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +102 -99
- package/dist/utils/validation.js.map +1 -1
- package/package.json +2 -2
|
@@ -3,6 +3,7 @@ import { getEnvApiKey } from "../env-api-keys.js";
|
|
|
3
3
|
import { calculateCost, clampThinkingLevel } from "../models.js";
|
|
4
4
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
5
5
|
import { shortHash } from "../utils/hash.js";
|
|
6
|
+
import { providerHeadersToRecord } from "../utils/headers.js";
|
|
6
7
|
import { parseStreamingJson } from "../utils/json-parse.js";
|
|
7
8
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
8
9
|
import { buildBaseOptions } from "./simple-options.js";
|
|
@@ -17,47 +18,51 @@ export const streamMistral = (model, context, options) => {
|
|
|
17
18
|
(async () => {
|
|
18
19
|
const output = createOutput(model);
|
|
19
20
|
try {
|
|
20
|
-
const
|
|
21
|
-
if (!apiKey) {
|
|
22
|
-
throw new Error(`No API key for provider: ${model.provider}`);
|
|
23
|
-
}
|
|
24
|
-
// Intentionally per-request: avoids shared SDK mutable state across concurrent consumers.
|
|
25
|
-
const mistral = new Mistral({
|
|
26
|
-
apiKey,
|
|
27
|
-
serverURL: model.baseUrl,
|
|
28
|
-
});
|
|
29
|
-
const normalizeMistralToolCallId = createMistralToolCallIdNormalizer();
|
|
30
|
-
const transformedMessages = transformMessages(context.messages, model, (id) => normalizeMistralToolCallId(id));
|
|
31
|
-
let payload = buildChatPayload(model, context, transformedMessages, options);
|
|
32
|
-
const nextPayload = await options?.onPayload?.(payload, model);
|
|
33
|
-
if (nextPayload !== undefined) {
|
|
34
|
-
payload = nextPayload;
|
|
35
|
-
}
|
|
36
|
-
const mistralStream = await mistral.chat.stream(payload, buildRequestOptions(model, options));
|
|
21
|
+
const mistralStream = await startMistralChatStream(model, context, options);
|
|
37
22
|
stream.push({ type: "start", partial: output });
|
|
38
23
|
await consumeChatStream(model, output, stream, mistralStream);
|
|
39
|
-
|
|
40
|
-
throw new Error("Request was aborted");
|
|
41
|
-
}
|
|
42
|
-
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
43
|
-
throw new Error("An unknown error occurred");
|
|
44
|
-
}
|
|
45
|
-
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
46
|
-
stream.end();
|
|
24
|
+
finishMistralChatStream(output, stream, options?.signal);
|
|
47
25
|
}
|
|
48
26
|
catch (error) {
|
|
49
|
-
|
|
50
|
-
// partialArgs is only a streaming scratch buffer; never persist it.
|
|
51
|
-
delete block.partialArgs;
|
|
52
|
-
}
|
|
53
|
-
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
54
|
-
output.errorMessage = formatMistralError(error);
|
|
55
|
-
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
56
|
-
stream.end();
|
|
27
|
+
failMistralChatStream(output, stream, error, options?.signal);
|
|
57
28
|
}
|
|
58
29
|
})();
|
|
59
30
|
return stream;
|
|
60
31
|
};
|
|
32
|
+
async function startMistralChatStream(model, context, options) {
|
|
33
|
+
const apiKey = options?.apiKey || getEnvApiKey(model.provider);
|
|
34
|
+
if (!apiKey)
|
|
35
|
+
throw new Error(`No API key for provider: ${model.provider}`);
|
|
36
|
+
const mistral = new Mistral({ apiKey, serverURL: model.baseUrl });
|
|
37
|
+
const normalizeMistralToolCallId = createMistralToolCallIdNormalizer();
|
|
38
|
+
const transformedMessages = transformMessages(context.messages, model, normalizeMistralToolCallId);
|
|
39
|
+
let payload = buildChatPayload(model, context, transformedMessages, options);
|
|
40
|
+
const nextPayload = await options?.onPayload?.(payload, model);
|
|
41
|
+
if (nextPayload !== undefined)
|
|
42
|
+
payload = nextPayload;
|
|
43
|
+
return mistral.chat.stream(payload, buildRequestOptions(model, options));
|
|
44
|
+
}
|
|
45
|
+
function finishMistralChatStream(output, stream, signal) {
|
|
46
|
+
if (signal?.aborted)
|
|
47
|
+
throw new Error("Request was aborted");
|
|
48
|
+
if (output.stopReason === "pending")
|
|
49
|
+
throw new Error("Mistral stream ended without a finish reason");
|
|
50
|
+
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
51
|
+
throw new Error("An unknown error occurred");
|
|
52
|
+
}
|
|
53
|
+
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
54
|
+
stream.end();
|
|
55
|
+
}
|
|
56
|
+
function failMistralChatStream(output, stream, error, signal) {
|
|
57
|
+
for (const block of output.content) {
|
|
58
|
+
// partialArgs is only a streaming scratch buffer; never persist it.
|
|
59
|
+
delete block.partialArgs;
|
|
60
|
+
}
|
|
61
|
+
output.stopReason = signal?.aborted ? "aborted" : "error";
|
|
62
|
+
output.errorMessage = formatMistralError(error);
|
|
63
|
+
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
64
|
+
stream.end();
|
|
65
|
+
}
|
|
61
66
|
/**
|
|
62
67
|
* Maps provider-agnostic `SimpleStreamOptions` to Mistral options.
|
|
63
68
|
*/
|
|
@@ -159,14 +164,12 @@ function buildRequestOptions(model, options) {
|
|
|
159
164
|
};
|
|
160
165
|
if (options?.signal)
|
|
161
166
|
requestOptions.signal = options.signal;
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (options?.headers)
|
|
166
|
-
Object.assign(headers, options.headers);
|
|
167
|
+
const explicitHeaders = { ...model.headers, ...options?.headers };
|
|
168
|
+
const hasExplicitAffinity = Object.keys(explicitHeaders).some((name) => name.toLowerCase() === "x-affinity");
|
|
169
|
+
const headers = providerHeadersToRecord(explicitHeaders) ?? {};
|
|
167
170
|
// Mistral infrastructure uses `x-affinity` for KV-cache reuse (prefix caching).
|
|
168
171
|
// Respect explicit caller-provided header values.
|
|
169
|
-
if (options?.sessionId && !
|
|
172
|
+
if (options?.sessionId && !hasExplicitAffinity) {
|
|
170
173
|
headers["x-affinity"] = options.sessionId;
|
|
171
174
|
}
|
|
172
175
|
if (Object.keys(headers).length > 0) {
|
|
@@ -200,174 +203,188 @@ function buildChatPayload(model, context, messages, options) {
|
|
|
200
203
|
}
|
|
201
204
|
return payload;
|
|
202
205
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
output
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
206
|
+
function currentMistralBlockIndex(state) {
|
|
207
|
+
return state.output.content.length - 1;
|
|
208
|
+
}
|
|
209
|
+
function finishCurrentMistralBlock(state) {
|
|
210
|
+
const block = state.currentBlock;
|
|
211
|
+
if (!block)
|
|
212
|
+
return;
|
|
213
|
+
if (block.type === "text") {
|
|
214
|
+
state.stream.push({
|
|
215
|
+
type: "text_end",
|
|
216
|
+
contentIndex: currentMistralBlockIndex(state),
|
|
217
|
+
content: block.text,
|
|
218
|
+
partial: state.output,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
state.stream.push({
|
|
223
|
+
type: "thinking_end",
|
|
224
|
+
contentIndex: currentMistralBlockIndex(state),
|
|
225
|
+
content: block.thinking,
|
|
226
|
+
partial: state.output,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
state.currentBlock = null;
|
|
230
|
+
}
|
|
231
|
+
function appendMistralTextDelta(state, delta) {
|
|
232
|
+
let block = state.currentBlock;
|
|
233
|
+
if (block?.type !== "text") {
|
|
234
|
+
finishCurrentMistralBlock(state);
|
|
235
|
+
block = { type: "text", text: "" };
|
|
236
|
+
state.currentBlock = block;
|
|
237
|
+
state.output.content.push(block);
|
|
238
|
+
state.stream.push({
|
|
239
|
+
type: "text_start",
|
|
240
|
+
contentIndex: currentMistralBlockIndex(state),
|
|
241
|
+
partial: state.output,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
block.text += delta;
|
|
245
|
+
state.stream.push({
|
|
246
|
+
type: "text_delta",
|
|
247
|
+
contentIndex: currentMistralBlockIndex(state),
|
|
248
|
+
delta,
|
|
249
|
+
partial: state.output,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
function appendMistralThinkingDelta(state, delta) {
|
|
253
|
+
let block = state.currentBlock;
|
|
254
|
+
if (block?.type !== "thinking") {
|
|
255
|
+
finishCurrentMistralBlock(state);
|
|
256
|
+
block = { type: "thinking", thinking: "" };
|
|
257
|
+
state.currentBlock = block;
|
|
258
|
+
state.output.content.push(block);
|
|
259
|
+
state.stream.push({
|
|
260
|
+
type: "thinking_start",
|
|
261
|
+
contentIndex: currentMistralBlockIndex(state),
|
|
262
|
+
partial: state.output,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
block.thinking += delta;
|
|
266
|
+
state.stream.push({
|
|
267
|
+
type: "thinking_delta",
|
|
268
|
+
contentIndex: currentMistralBlockIndex(state),
|
|
269
|
+
delta,
|
|
270
|
+
partial: state.output,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
function consumeMistralContentDelta(state, content) {
|
|
274
|
+
if (content === null || content === undefined)
|
|
275
|
+
return;
|
|
276
|
+
const items = typeof content === "string" ? [content] : content;
|
|
277
|
+
for (const item of items) {
|
|
278
|
+
if (typeof item === "string") {
|
|
279
|
+
appendMistralTextDelta(state, sanitizeSurrogates(item));
|
|
244
280
|
continue;
|
|
245
|
-
if (choice.finishReason) {
|
|
246
|
-
output.stopReason = mapChatStopReason(choice.finishReason);
|
|
247
281
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
output.content.push(currentBlock);
|
|
258
|
-
stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
|
|
259
|
-
}
|
|
260
|
-
currentBlock.text += textDelta;
|
|
261
|
-
stream.push({
|
|
262
|
-
type: "text_delta",
|
|
263
|
-
contentIndex: blockIndex(),
|
|
264
|
-
delta: textDelta,
|
|
265
|
-
partial: output,
|
|
266
|
-
});
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (item.type === "thinking") {
|
|
270
|
-
const deltaText = item.thinking
|
|
271
|
-
.map((part) => ("text" in part ? part.text : ""))
|
|
272
|
-
.filter((text) => text.length > 0)
|
|
273
|
-
.join("");
|
|
274
|
-
const thinkingDelta = sanitizeSurrogates(deltaText);
|
|
275
|
-
if (!thinkingDelta)
|
|
276
|
-
continue;
|
|
277
|
-
if (!currentBlock || currentBlock.type !== "thinking") {
|
|
278
|
-
finishCurrentBlock(currentBlock);
|
|
279
|
-
currentBlock = { type: "thinking", thinking: "" };
|
|
280
|
-
output.content.push(currentBlock);
|
|
281
|
-
stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
|
|
282
|
-
}
|
|
283
|
-
currentBlock.thinking += thinkingDelta;
|
|
284
|
-
stream.push({
|
|
285
|
-
type: "thinking_delta",
|
|
286
|
-
contentIndex: blockIndex(),
|
|
287
|
-
delta: thinkingDelta,
|
|
288
|
-
partial: output,
|
|
289
|
-
});
|
|
290
|
-
continue;
|
|
291
|
-
}
|
|
292
|
-
if (item.type === "text") {
|
|
293
|
-
const textDelta = sanitizeSurrogates(item.text);
|
|
294
|
-
if (!currentBlock || currentBlock.type !== "text") {
|
|
295
|
-
finishCurrentBlock(currentBlock);
|
|
296
|
-
currentBlock = { type: "text", text: "" };
|
|
297
|
-
output.content.push(currentBlock);
|
|
298
|
-
stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
|
|
299
|
-
}
|
|
300
|
-
currentBlock.text += textDelta;
|
|
301
|
-
stream.push({
|
|
302
|
-
type: "text_delta",
|
|
303
|
-
contentIndex: blockIndex(),
|
|
304
|
-
delta: textDelta,
|
|
305
|
-
partial: output,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
}
|
|
282
|
+
if (item.type === "thinking") {
|
|
283
|
+
const delta = item.thinking
|
|
284
|
+
.map((part) => ("text" in part ? part.text : ""))
|
|
285
|
+
.filter((text) => text.length > 0)
|
|
286
|
+
.join("");
|
|
287
|
+
const sanitized = sanitizeSurrogates(delta);
|
|
288
|
+
if (sanitized)
|
|
289
|
+
appendMistralThinkingDelta(state, sanitized);
|
|
290
|
+
continue;
|
|
309
291
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
};
|
|
336
|
-
output.content.push(block);
|
|
337
|
-
toolBlocksByKey.set(key, output.content.length - 1);
|
|
338
|
-
stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
|
|
339
|
-
}
|
|
340
|
-
const argsDelta = typeof toolCall.function.arguments === "string"
|
|
341
|
-
? toolCall.function.arguments
|
|
342
|
-
: JSON.stringify(toolCall.function.arguments || {});
|
|
343
|
-
block.partialArgs = (block.partialArgs || "") + argsDelta;
|
|
344
|
-
block.arguments = parseStreamingJson(block.partialArgs);
|
|
345
|
-
stream.push({
|
|
346
|
-
type: "toolcall_delta",
|
|
347
|
-
contentIndex: toolBlocksByKey.get(key),
|
|
348
|
-
delta: argsDelta,
|
|
349
|
-
partial: output,
|
|
350
|
-
});
|
|
292
|
+
if (item.type === "text")
|
|
293
|
+
appendMistralTextDelta(state, sanitizeSurrogates(item.text));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function updateMistralChunkMetadata(state, chunk) {
|
|
297
|
+
state.output.responseId ||= chunk.id;
|
|
298
|
+
if (!chunk.usage)
|
|
299
|
+
return;
|
|
300
|
+
state.output.usage.input = chunk.usage.promptTokens || 0;
|
|
301
|
+
state.output.usage.output = chunk.usage.completionTokens || 0;
|
|
302
|
+
state.output.usage.cacheRead = 0;
|
|
303
|
+
state.output.usage.cacheWrite = 0;
|
|
304
|
+
state.output.usage.totalTokens = chunk.usage.totalTokens || state.output.usage.input + state.output.usage.output;
|
|
305
|
+
calculateCost(state.model, state.output.usage);
|
|
306
|
+
}
|
|
307
|
+
function resolveMistralToolBlock(state, toolCall) {
|
|
308
|
+
const callId = toolCall.id && toolCall.id !== "null"
|
|
309
|
+
? toolCall.id
|
|
310
|
+
: deriveMistralToolCallId(`toolcall:${toolCall.index ?? 0}`, 0);
|
|
311
|
+
const key = `${callId}:${toolCall.index || 0}`;
|
|
312
|
+
const existingIndex = state.toolBlocksByKey.get(key);
|
|
313
|
+
if (existingIndex !== undefined) {
|
|
314
|
+
const existing = state.output.content[existingIndex];
|
|
315
|
+
if (existing?.type === "toolCall") {
|
|
316
|
+
return { block: existing, index: existingIndex };
|
|
351
317
|
}
|
|
352
318
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
319
|
+
const block = {
|
|
320
|
+
type: "toolCall",
|
|
321
|
+
id: callId,
|
|
322
|
+
name: toolCall.function.name,
|
|
323
|
+
arguments: {},
|
|
324
|
+
partialArgs: "",
|
|
325
|
+
};
|
|
326
|
+
state.output.content.push(block);
|
|
327
|
+
const index = state.output.content.length - 1;
|
|
328
|
+
state.toolBlocksByKey.set(key, index);
|
|
329
|
+
state.stream.push({ type: "toolcall_start", contentIndex: index, partial: state.output });
|
|
330
|
+
return { block, index };
|
|
331
|
+
}
|
|
332
|
+
function consumeMistralToolCall(state, toolCall) {
|
|
333
|
+
finishCurrentMistralBlock(state);
|
|
334
|
+
const { block, index } = resolveMistralToolBlock(state, toolCall);
|
|
335
|
+
const argsDelta = typeof toolCall.function.arguments === "string"
|
|
336
|
+
? toolCall.function.arguments
|
|
337
|
+
: JSON.stringify(toolCall.function.arguments || {});
|
|
338
|
+
block.partialArgs = (block.partialArgs || "") + argsDelta;
|
|
339
|
+
block.arguments = parseStreamingJson(block.partialArgs);
|
|
340
|
+
state.stream.push({
|
|
341
|
+
type: "toolcall_delta",
|
|
342
|
+
contentIndex: index,
|
|
343
|
+
delta: argsDelta,
|
|
344
|
+
partial: state.output,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
function consumeMistralChoice(state, choice) {
|
|
348
|
+
if (choice.finishReason)
|
|
349
|
+
state.output.stopReason = mapChatStopReason(choice.finishReason);
|
|
350
|
+
consumeMistralContentDelta(state, choice.delta.content);
|
|
351
|
+
for (const toolCall of choice.delta.toolCalls || [])
|
|
352
|
+
consumeMistralToolCall(state, toolCall);
|
|
353
|
+
}
|
|
354
|
+
function finalizeMistralToolCalls(state) {
|
|
355
|
+
for (const index of state.toolBlocksByKey.values()) {
|
|
356
|
+
const block = state.output.content[index];
|
|
356
357
|
if (block.type !== "toolCall")
|
|
357
358
|
continue;
|
|
358
359
|
const toolBlock = block;
|
|
359
360
|
toolBlock.arguments = parseStreamingJson(toolBlock.partialArgs);
|
|
360
|
-
// Finalize in-place and strip the scratch buffer so replay only
|
|
361
|
-
// carries parsed arguments.
|
|
362
361
|
delete toolBlock.partialArgs;
|
|
363
|
-
stream.push({
|
|
362
|
+
state.stream.push({
|
|
364
363
|
type: "toolcall_end",
|
|
365
364
|
contentIndex: index,
|
|
366
365
|
toolCall: toolBlock,
|
|
367
|
-
partial: output,
|
|
366
|
+
partial: state.output,
|
|
368
367
|
});
|
|
369
368
|
}
|
|
370
369
|
}
|
|
370
|
+
async function consumeChatStream(model, output, stream, mistralStream) {
|
|
371
|
+
const state = {
|
|
372
|
+
model,
|
|
373
|
+
output,
|
|
374
|
+
stream,
|
|
375
|
+
currentBlock: null,
|
|
376
|
+
toolBlocksByKey: new Map(),
|
|
377
|
+
};
|
|
378
|
+
for await (const event of mistralStream) {
|
|
379
|
+
const chunk = event.data;
|
|
380
|
+
updateMistralChunkMetadata(state, chunk);
|
|
381
|
+
const choice = chunk.choices[0];
|
|
382
|
+
if (choice)
|
|
383
|
+
consumeMistralChoice(state, choice);
|
|
384
|
+
}
|
|
385
|
+
finishCurrentMistralBlock(state);
|
|
386
|
+
finalizeMistralToolCalls(state);
|
|
387
|
+
}
|
|
371
388
|
function toFunctionTools(tools) {
|
|
372
389
|
return tools.map((tool) => ({
|
|
373
390
|
type: "function",
|
|
@@ -392,90 +409,95 @@ function stripSymbolKeys(value) {
|
|
|
392
409
|
}
|
|
393
410
|
return value;
|
|
394
411
|
}
|
|
395
|
-
function
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const contentParts = [];
|
|
422
|
-
const toolCalls = [];
|
|
423
|
-
for (const block of msg.content) {
|
|
424
|
-
if (block.type === "text") {
|
|
425
|
-
if (block.text.trim().length > 0) {
|
|
426
|
-
contentParts.push({ type: "text", text: sanitizeSurrogates(block.text) });
|
|
427
|
-
}
|
|
428
|
-
continue;
|
|
412
|
+
function convertMistralUserMessage(message, supportsImages) {
|
|
413
|
+
if (typeof message.content === "string") {
|
|
414
|
+
return { role: "user", content: sanitizeSurrogates(message.content) };
|
|
415
|
+
}
|
|
416
|
+
const hadImages = message.content.some((item) => item.type === "image");
|
|
417
|
+
const content = message.content
|
|
418
|
+
.filter((item) => item.type === "text" || supportsImages)
|
|
419
|
+
.map((item) => {
|
|
420
|
+
if (item.type === "text")
|
|
421
|
+
return { type: "text", text: sanitizeSurrogates(item.text) };
|
|
422
|
+
return { type: "image_url", imageUrl: `data:${item.mimeType};base64,${item.data}` };
|
|
423
|
+
});
|
|
424
|
+
if (content.length > 0)
|
|
425
|
+
return { role: "user", content };
|
|
426
|
+
return hadImages && !supportsImages
|
|
427
|
+
? { role: "user", content: "(image omitted: model does not support images)" }
|
|
428
|
+
: undefined;
|
|
429
|
+
}
|
|
430
|
+
function convertMistralAssistantMessage(message) {
|
|
431
|
+
const contentParts = [];
|
|
432
|
+
const toolCalls = [];
|
|
433
|
+
for (const block of message.content) {
|
|
434
|
+
switch (block.type) {
|
|
435
|
+
case "text":
|
|
436
|
+
if (block.text.trim().length > 0) {
|
|
437
|
+
contentParts.push({ type: "text", text: sanitizeSurrogates(block.text) });
|
|
429
438
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
continue;
|
|
439
|
+
break;
|
|
440
|
+
case "thinking":
|
|
441
|
+
if (block.thinking.trim().length > 0) {
|
|
442
|
+
contentParts.push({
|
|
443
|
+
type: "thinking",
|
|
444
|
+
thinking: [{ type: "text", text: sanitizeSurrogates(block.thinking) }],
|
|
445
|
+
});
|
|
438
446
|
}
|
|
447
|
+
break;
|
|
448
|
+
case "toolCall":
|
|
439
449
|
toolCalls.push({
|
|
440
450
|
id: block.id,
|
|
441
451
|
type: "function",
|
|
442
452
|
function: { name: block.name, arguments: JSON.stringify(block.arguments || {}) },
|
|
443
453
|
});
|
|
444
|
-
|
|
445
|
-
const assistantMessage = { role: "assistant" };
|
|
446
|
-
if (contentParts.length > 0)
|
|
447
|
-
assistantMessage.content = contentParts;
|
|
448
|
-
if (toolCalls.length > 0)
|
|
449
|
-
assistantMessage.toolCalls = toolCalls;
|
|
450
|
-
if (contentParts.length > 0 || toolCalls.length > 0)
|
|
451
|
-
result.push(assistantMessage);
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
const toolContent = [];
|
|
455
|
-
const textResult = msg.content
|
|
456
|
-
.filter((part) => part.type === "text")
|
|
457
|
-
.map((part) => (part.type === "text" ? sanitizeSurrogates(part.text) : ""))
|
|
458
|
-
.join("\n");
|
|
459
|
-
const hasImages = msg.content.some((part) => part.type === "image");
|
|
460
|
-
const toolText = buildToolResultText(textResult, hasImages, supportsImages, msg.isError);
|
|
461
|
-
toolContent.push({ type: "text", text: toolText });
|
|
462
|
-
for (const part of msg.content) {
|
|
463
|
-
if (!supportsImages)
|
|
464
|
-
continue;
|
|
465
|
-
if (part.type !== "image")
|
|
466
|
-
continue;
|
|
467
|
-
toolContent.push({
|
|
468
|
-
type: "image_url",
|
|
469
|
-
imageUrl: `data:${part.mimeType};base64,${part.data}`,
|
|
470
|
-
});
|
|
454
|
+
break;
|
|
471
455
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
456
|
+
}
|
|
457
|
+
const assistantMessage = { role: "assistant" };
|
|
458
|
+
if (contentParts.length > 0)
|
|
459
|
+
assistantMessage.content = contentParts;
|
|
460
|
+
if (toolCalls.length > 0)
|
|
461
|
+
assistantMessage.toolCalls = toolCalls;
|
|
462
|
+
return contentParts.length > 0 || toolCalls.length > 0 ? assistantMessage : undefined;
|
|
463
|
+
}
|
|
464
|
+
function convertMistralToolResultMessage(message, supportsImages) {
|
|
465
|
+
const toolContent = [];
|
|
466
|
+
const textResult = message.content
|
|
467
|
+
.filter((part) => part.type === "text")
|
|
468
|
+
.map((part) => (part.type === "text" ? sanitizeSurrogates(part.text) : ""))
|
|
469
|
+
.join("\n");
|
|
470
|
+
const hasImages = message.content.some((part) => part.type === "image");
|
|
471
|
+
const toolText = buildToolResultText(textResult, hasImages, supportsImages, message.isError);
|
|
472
|
+
toolContent.push({ type: "text", text: toolText });
|
|
473
|
+
for (const part of message.content) {
|
|
474
|
+
if (!supportsImages)
|
|
475
|
+
continue;
|
|
476
|
+
if (part.type !== "image")
|
|
477
|
+
continue;
|
|
478
|
+
toolContent.push({
|
|
479
|
+
type: "image_url",
|
|
480
|
+
imageUrl: `data:${part.mimeType};base64,${part.data}`,
|
|
477
481
|
});
|
|
478
482
|
}
|
|
483
|
+
return {
|
|
484
|
+
role: "tool",
|
|
485
|
+
toolCallId: message.toolCallId,
|
|
486
|
+
name: message.toolName,
|
|
487
|
+
content: toolContent,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function toChatMessages(messages, supportsImages) {
|
|
491
|
+
const result = [];
|
|
492
|
+
for (const message of messages) {
|
|
493
|
+
const converted = message.role === "user"
|
|
494
|
+
? convertMistralUserMessage(message, supportsImages)
|
|
495
|
+
: message.role === "assistant"
|
|
496
|
+
? convertMistralAssistantMessage(message)
|
|
497
|
+
: convertMistralToolResultMessage(message, supportsImages);
|
|
498
|
+
if (converted)
|
|
499
|
+
result.push(converted);
|
|
500
|
+
}
|
|
479
501
|
return result;
|
|
480
502
|
}
|
|
481
503
|
function buildToolResultText(text, hasImages, supportsImages, isError) {
|