fix(provider): preserve Mistral reasoning history (#38453)
This commit is contained in:
parent
62e4641235
commit
20589d66d5
8 changed files with 1084 additions and 94 deletions
14
bun.lock
14
bun.lock
|
|
@ -303,7 +303,7 @@
|
|||
"@ai-sdk/google": "3.0.73",
|
||||
"@ai-sdk/google-vertex": "4.0.128",
|
||||
"@ai-sdk/groq": "3.0.31",
|
||||
"@ai-sdk/mistral": "3.0.34",
|
||||
"@ai-sdk/mistral": "3.0.51",
|
||||
"@ai-sdk/openai": "3.0.84",
|
||||
"@ai-sdk/openai-compatible": "2.0.41",
|
||||
"@ai-sdk/perplexity": "3.0.26",
|
||||
|
|
@ -578,7 +578,7 @@
|
|||
"@ai-sdk/google": "3.0.73",
|
||||
"@ai-sdk/google-vertex": "4.0.128",
|
||||
"@ai-sdk/groq": "3.0.31",
|
||||
"@ai-sdk/mistral": "3.0.34",
|
||||
"@ai-sdk/mistral": "3.0.51",
|
||||
"@ai-sdk/openai": "3.0.84",
|
||||
"@ai-sdk/openai-compatible": "2.0.41",
|
||||
"@ai-sdk/perplexity": "3.0.26",
|
||||
|
|
@ -1076,12 +1076,12 @@
|
|||
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
|
||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
|
||||
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
|
||||
"@ai-sdk/mistral@3.0.51": "patches/@ai-sdk%2Fmistral@3.0.51.patch",
|
||||
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
||||
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
|
||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
||||
"@ai-sdk/mistral@3.0.34": "patches/@ai-sdk%2Fmistral@3.0.34.patch",
|
||||
},
|
||||
"overrides": {
|
||||
"@opentui/core": "catalog:",
|
||||
|
|
@ -1202,7 +1202,7 @@
|
|||
|
||||
"@ai-sdk/groq": ["@ai-sdk/groq@3.0.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XbbugpnFmXGu2TlXiq8KUJskP6/VVbuFcnFIGDzDIB/Chg6XHsNnqrTF80Zxkh0Pd3+NvbM+2Uqrtsndk6bDAg=="],
|
||||
|
||||
"@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.34", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HpK28sWGdIfg1vTSScJNtzVdvNRfA4mfCmPmPR+j/MGJ0oAuEJMqxWkL96ZnGPdhZt5KdW09aKovdIe+q2zQ7A=="],
|
||||
"@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.51", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-83eXY6p0lUFhSuMvNDmTKDuMciK5XDAWDlNh5c0L80tKjmtCFRItA1MZHp4IKe1r7eK8Rb5nN7qtxqMLUFRIRw=="],
|
||||
|
||||
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg=="],
|
||||
|
||||
|
|
@ -5702,9 +5702,9 @@
|
|||
|
||||
"@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"@ai-sdk/mistral/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||
"@ai-sdk/mistral/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
|
||||
|
||||
"@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="],
|
||||
"@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="],
|
||||
|
||||
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@
|
|||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
||||
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
|
||||
"@ai-sdk/mistral@3.0.34": "patches/@ai-sdk%2Fmistral@3.0.34.patch",
|
||||
"@ai-sdk/mistral@3.0.51": "patches/@ai-sdk%2Fmistral@3.0.51.patch",
|
||||
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
|
||||
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@
|
|||
"@ai-sdk/google": "3.0.73",
|
||||
"@ai-sdk/google-vertex": "4.0.128",
|
||||
"@ai-sdk/groq": "3.0.31",
|
||||
"@ai-sdk/mistral": "3.0.34",
|
||||
"@ai-sdk/mistral": "3.0.51",
|
||||
"@ai-sdk/openai": "3.0.84",
|
||||
"@ai-sdk/openai-compatible": "2.0.41",
|
||||
"@ai-sdk/perplexity": "3.0.26",
|
||||
|
|
|
|||
|
|
@ -26,3 +26,257 @@ test("Mistral sends promptCacheKey as prompt_cache_key", async () => {
|
|||
|
||||
expect(body?.prompt_cache_key).toBe("session-123")
|
||||
})
|
||||
|
||||
test("Mistral round-trips native reasoning in assistant history", async () => {
|
||||
let body: { messages?: unknown[] } | undefined
|
||||
const mockFetch = Object.assign(
|
||||
async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
|
||||
body = JSON.parse(String(init?.body))
|
||||
return Response.json({
|
||||
id: "response-1",
|
||||
created: 0,
|
||||
model: "mistral-small-latest",
|
||||
object: "chat.completion",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: [
|
||||
{ type: "text", text: "The user is greeting me." },
|
||||
{
|
||||
type: "tool_reference",
|
||||
tool: "web_search",
|
||||
title: "Example result",
|
||||
url: "https://example.com/tool",
|
||||
favicon: "https://example.com/favicon.ico",
|
||||
description: "Example description",
|
||||
},
|
||||
{ type: "reference", reference_ids: [1, "source-2"] },
|
||||
],
|
||||
closed: true,
|
||||
signature: "sig-123",
|
||||
},
|
||||
{ type: "text", text: "Hi" },
|
||||
],
|
||||
},
|
||||
finish_reason: "stop",
|
||||
},
|
||||
],
|
||||
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
|
||||
})
|
||||
},
|
||||
{ preconnect: fetch.preconnect },
|
||||
)
|
||||
const model = createMistral({ apiKey: "test", fetch: mockFetch })("mistral-small-latest")
|
||||
|
||||
const first = await model.doGenerate({
|
||||
prompt: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
|
||||
})
|
||||
const reasoning = first.content.find((part) => part.type === "reasoning")
|
||||
const text = first.content.find((part) => part.type === "text")
|
||||
if (!reasoning || !text) throw new Error("expected reasoning and text")
|
||||
|
||||
await model.doGenerate({
|
||||
prompt: [
|
||||
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ ...reasoning, providerOptions: reasoning.providerMetadata }, text],
|
||||
},
|
||||
{ role: "user", content: [{ type: "text", text: "Hello again" }] },
|
||||
],
|
||||
})
|
||||
|
||||
expect(body?.messages?.[1]).toEqual({
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: [
|
||||
{ type: "text", text: "The user is greeting me." },
|
||||
{
|
||||
type: "tool_reference",
|
||||
tool: "web_search",
|
||||
title: "Example result",
|
||||
url: "https://example.com/tool",
|
||||
favicon: "https://example.com/favicon.ico",
|
||||
description: "Example description",
|
||||
},
|
||||
{ type: "reference", reference_ids: [1, "source-2"] },
|
||||
],
|
||||
closed: true,
|
||||
signature: "sig-123",
|
||||
},
|
||||
{ type: "text", text: "Hi" },
|
||||
],
|
||||
})
|
||||
|
||||
await model.doGenerate({
|
||||
prompt: [
|
||||
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "reasoning", text: "thinking" },
|
||||
{ type: "text", text: "Hi" },
|
||||
],
|
||||
},
|
||||
{ role: "user", content: [{ type: "text", text: "Hello again" }] },
|
||||
],
|
||||
})
|
||||
expect(body?.messages?.[1]).toEqual({ role: "assistant", content: "thinkingHi" })
|
||||
})
|
||||
|
||||
test("Mistral preserves native reasoning metadata while streaming", async () => {
|
||||
const chunks = [
|
||||
{
|
||||
id: "response-1",
|
||||
created: 0,
|
||||
model: "mistral-small-latest",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: [
|
||||
{ type: "text", text: "thinking" },
|
||||
{
|
||||
type: "tool_reference",
|
||||
tool: "web_search",
|
||||
title: "Example result",
|
||||
url: "https://example.com/tool",
|
||||
favicon: "https://example.com/favicon.ico",
|
||||
description: "Example description",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "response-1",
|
||||
created: 0,
|
||||
model: "mistral-small-latest",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: [{ type: "reference", reference_ids: [1, "source-2"] }],
|
||||
closed: true,
|
||||
signature: "sig-123",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "response-1",
|
||||
created: 0,
|
||||
model: "mistral-small-latest",
|
||||
choices: [{ index: 0, delta: { content: [{ type: "text", text: "answer" }] } }],
|
||||
},
|
||||
{
|
||||
id: "response-1",
|
||||
created: 0,
|
||||
model: "mistral-small-latest",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
|
||||
},
|
||||
]
|
||||
const mockFetch = Object.assign(
|
||||
async () =>
|
||||
new Response(chunks.map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`).join(""), {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
}),
|
||||
{ preconnect: fetch.preconnect },
|
||||
)
|
||||
const model = createMistral({ apiKey: "test", fetch: mockFetch })("mistral-small-latest")
|
||||
const result = await model.doStream({
|
||||
prompt: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
|
||||
})
|
||||
const events = []
|
||||
for await (const event of result.stream) events.push(event)
|
||||
|
||||
expect(events.find((event) => event.type === "reasoning-end")?.providerMetadata).toEqual({
|
||||
mistral: {
|
||||
thinking: {
|
||||
type: "thinking",
|
||||
thinking: [
|
||||
{ type: "text", text: "thinking" },
|
||||
{
|
||||
type: "tool_reference",
|
||||
tool: "web_search",
|
||||
title: "Example result",
|
||||
url: "https://example.com/tool",
|
||||
favicon: "https://example.com/favicon.ico",
|
||||
description: "Example description",
|
||||
},
|
||||
{ type: "reference", reference_ids: [1, "source-2"] },
|
||||
],
|
||||
closed: true,
|
||||
signature: "sig-123",
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(
|
||||
events
|
||||
.filter((event) => event.type === "reasoning-start" || event.type === "reasoning-delta")
|
||||
.every((event) => event.providerMetadata === undefined),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test("Mistral preserves metadata-only thinking chunks", async () => {
|
||||
const thinking = {
|
||||
type: "thinking" as const,
|
||||
thinking: [
|
||||
{
|
||||
type: "tool_reference",
|
||||
tool: "web_search",
|
||||
title: "Example result",
|
||||
url: "https://example.com/tool",
|
||||
favicon: "https://example.com/favicon.ico",
|
||||
description: "Example description",
|
||||
},
|
||||
{ type: "reference", reference_ids: [1, "source-2"] },
|
||||
],
|
||||
closed: true,
|
||||
signature: "sig-123",
|
||||
}
|
||||
const mockFetch = Object.assign(
|
||||
async () =>
|
||||
Response.json({
|
||||
id: "response-1",
|
||||
created: 0,
|
||||
model: "mistral-small-latest",
|
||||
object: "chat.completion",
|
||||
choices: [{ index: 0, message: { role: "assistant", content: [thinking] }, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
|
||||
}),
|
||||
{ preconnect: fetch.preconnect },
|
||||
)
|
||||
const model = createMistral({ apiKey: "test", fetch: mockFetch })("mistral-small-latest")
|
||||
const result = await model.doGenerate({
|
||||
prompt: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
|
||||
})
|
||||
|
||||
expect(result.content).toEqual([
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "",
|
||||
providerMetadata: { mistral: { thinking } },
|
||||
},
|
||||
])
|
||||
})
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
"@ai-sdk/google": "3.0.73",
|
||||
"@ai-sdk/google-vertex": "4.0.128",
|
||||
"@ai-sdk/groq": "3.0.31",
|
||||
"@ai-sdk/mistral": "3.0.34",
|
||||
"@ai-sdk/mistral": "3.0.51",
|
||||
"@ai-sdk/openai": "3.0.84",
|
||||
"@ai-sdk/openai-compatible": "2.0.41",
|
||||
"@ai-sdk/perplexity": "3.0.26",
|
||||
|
|
|
|||
|
|
@ -906,6 +906,117 @@ describe("session.llm.stream", () => {
|
|||
},
|
||||
)
|
||||
|
||||
const mistralFixture = { providerID: "mistral", modelID: "mistral-small-latest" }
|
||||
it.instance(
|
||||
"replays native Mistral reasoning from chat history",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const fixture = loadFixture(mistralFixture.providerID, mistralFixture.modelID)
|
||||
const request = waitRequest(
|
||||
"/chat/completions",
|
||||
createEventResponse(
|
||||
[
|
||||
{
|
||||
id: "chatcmpl-mistral",
|
||||
object: "chat.completion.chunk",
|
||||
created: 0,
|
||||
model: fixture.model.id,
|
||||
choices: [{ index: 0, delta: { role: "assistant", content: "Hello" } }],
|
||||
},
|
||||
{
|
||||
id: "chatcmpl-mistral",
|
||||
object: "chat.completion.chunk",
|
||||
created: 0,
|
||||
model: fixture.model.id,
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
},
|
||||
],
|
||||
true,
|
||||
),
|
||||
)
|
||||
|
||||
const resolved = yield* Provider.use.getModel(
|
||||
ProviderV2.ID.make(mistralFixture.providerID),
|
||||
ModelV2.ID.make(fixture.model.id),
|
||||
)
|
||||
const sessionID = SessionID.make("session-test-mistral-reasoning")
|
||||
const agent = {
|
||||
name: "test",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
permission: [{ permission: "*", pattern: "*", action: "allow" }],
|
||||
} satisfies Agent.Info
|
||||
|
||||
const user = {
|
||||
id: MessageID.make("msg_user-mistral-reasoning"),
|
||||
sessionID,
|
||||
role: "user",
|
||||
time: { created: Date.now() },
|
||||
agent: agent.name,
|
||||
model: { providerID: ProviderV2.ID.make(mistralFixture.providerID), modelID: resolved.id },
|
||||
} satisfies SessionV1.User
|
||||
|
||||
const thinking = {
|
||||
type: "thinking",
|
||||
thinking: [
|
||||
{ type: "text", text: "thinking" },
|
||||
{
|
||||
type: "tool_reference",
|
||||
tool: "web_search",
|
||||
title: "Example result",
|
||||
url: "https://example.com/tool",
|
||||
favicon: "https://example.com/favicon.ico",
|
||||
description: "Example description",
|
||||
},
|
||||
{ type: "reference", reference_ids: [1, "source-2"] },
|
||||
],
|
||||
closed: true,
|
||||
signature: "sig-123",
|
||||
}
|
||||
|
||||
yield* drain({
|
||||
user,
|
||||
sessionID,
|
||||
model: resolved,
|
||||
agent,
|
||||
system: ["You are a helpful assistant."],
|
||||
messages: [
|
||||
{ role: "user", content: "Hello" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "thinking",
|
||||
providerOptions: { mistral: { thinking } },
|
||||
},
|
||||
{ type: "text", text: "Previous answer" },
|
||||
],
|
||||
},
|
||||
{ role: "user", content: "Continue" },
|
||||
] satisfies ModelMessage[],
|
||||
tools: {},
|
||||
})
|
||||
|
||||
const capture = yield* Effect.promise(() => request)
|
||||
const messages = capture.body.messages as Array<Record<string, unknown>>
|
||||
expect(messages.find((message) => message.role === "assistant")).toEqual({
|
||||
role: "assistant",
|
||||
content: [thinking, { type: "text", text: "Previous answer" }],
|
||||
})
|
||||
}),
|
||||
{
|
||||
config: () => ({
|
||||
enabled_providers: [mistralFixture.providerID],
|
||||
provider: {
|
||||
[mistralFixture.providerID]: {
|
||||
options: { apiKey: "test-key", baseURL: `${state.server!.url.origin}/v1` },
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
)
|
||||
|
||||
const alibabaQwenFixture = { providerID: "alibaba", modelID: "qwen-plus" }
|
||||
it.instance(
|
||||
"service stream cancellation cancels provider response body promptly",
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
diff --git a/dist/index.d.ts b/dist/index.d.ts
|
||||
index 1ca9113bed2728a616db773a8e08d8d6957447d7..15408ec429dc210b5fa43589d81b69c93bf27b2d 100644
|
||||
--- a/dist/index.d.ts
|
||||
+++ b/dist/index.d.ts
|
||||
@@ -14,6 +14,7 @@ declare const mistralLanguageModelOptions: z.ZodObject<{
|
||||
none: "none";
|
||||
high: "high";
|
||||
}>>;
|
||||
+ promptCacheKey: z.ZodOptional<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
type MistralLanguageModelOptions = z.infer<typeof mistralLanguageModelOptions>;
|
||||
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 45735e524aaff54ea058c99c729c5ffd3c507058..6aca5f6f13da0054ede31c1f1a692e4eaed37d34 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -268,7 +268,8 @@ var mistralLanguageModelOptions = import_v4.z.object({
|
||||
* - `'high'`: Enable reasoning
|
||||
* - `'none'`: Disable reasoning
|
||||
*/
|
||||
- reasoningEffort: import_v4.z.enum(["high", "none"]).optional()
|
||||
+ reasoningEffort: import_v4.z.enum(["high", "none"]).optional(),
|
||||
+ promptCacheKey: import_v4.z.string().optional()
|
||||
});
|
||||
|
||||
// src/mistral-error.ts
|
||||
@@ -413,6 +414,7 @@ var MistralChatLanguageModel = class {
|
||||
top_p: topP,
|
||||
random_seed: seed,
|
||||
reasoning_effort: options.reasoningEffort,
|
||||
+ prompt_cache_key: options.promptCacheKey,
|
||||
// response format:
|
||||
response_format: (responseFormat == null ? void 0 : responseFormat.type) === "json" ? structuredOutputs && (responseFormat == null ? void 0 : responseFormat.schema) != null ? {
|
||||
type: "json_schema",
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index 4c22df1cd78a1ba81309c8a86ceecefef4ba4aea..30cd3b1f503860109b7fa2107cd1eb17b70c96be 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -256,7 +256,8 @@ var mistralLanguageModelOptions = z.object({
|
||||
* - `'high'`: Enable reasoning
|
||||
* - `'none'`: Disable reasoning
|
||||
*/
|
||||
- reasoningEffort: z.enum(["high", "none"]).optional()
|
||||
+ reasoningEffort: z.enum(["high", "none"]).optional(),
|
||||
+ promptCacheKey: z.string().optional()
|
||||
});
|
||||
|
||||
// src/mistral-error.ts
|
||||
@@ -403,6 +404,7 @@ var MistralChatLanguageModel = class {
|
||||
top_p: topP,
|
||||
random_seed: seed,
|
||||
reasoning_effort: options.reasoningEffort,
|
||||
+ prompt_cache_key: options.promptCacheKey,
|
||||
// response format:
|
||||
response_format: (responseFormat == null ? void 0 : responseFormat.type) === "json" ? structuredOutputs && (responseFormat == null ? void 0 : responseFormat.schema) != null ? {
|
||||
type: "json_schema",
|
||||
diff --git a/src/mistral-chat-language-model.ts b/src/mistral-chat-language-model.ts
|
||||
index 480c472d534bedbe8897979673453bd1c29a70b7..e46496da94f7d4af9822897202ca6baae67dae3a 100644
|
||||
--- a/src/mistral-chat-language-model.ts
|
||||
+++ b/src/mistral-chat-language-model.ts
|
||||
@@ -129,6 +129,7 @@ export class MistralChatLanguageModel implements LanguageModelV3 {
|
||||
top_p: topP,
|
||||
random_seed: seed,
|
||||
reasoning_effort: options.reasoningEffort,
|
||||
+ prompt_cache_key: options.promptCacheKey,
|
||||
|
||||
// response format:
|
||||
response_format:
|
||||
diff --git a/src/mistral-chat-options.ts b/src/mistral-chat-options.ts
|
||||
index 80fff45fba2c378fa06962f071946bcd2b882a0b..b4fdfa51f3bf11a4220e010c8aca92482cb0c3db 100644
|
||||
--- a/src/mistral-chat-options.ts
|
||||
+++ b/src/mistral-chat-options.ts
|
||||
@@ -62,6 +62,11 @@ export const mistralLanguageModelOptions = z.object({
|
||||
* - `'none'`: Disable reasoning
|
||||
*/
|
||||
reasoningEffort: z.enum(['high', 'none']).optional(),
|
||||
+
|
||||
+ /**
|
||||
+ * A stable identifier used to route requests with shared prompt prefixes.
|
||||
+ */
|
||||
+ promptCacheKey: z.string().optional(),
|
||||
});
|
||||
|
||||
export type MistralLanguageModelOptions = z.infer<
|
||||
709
patches/@ai-sdk%2Fmistral@3.0.51.patch
Normal file
709
patches/@ai-sdk%2Fmistral@3.0.51.patch
Normal file
|
|
@ -0,0 +1,709 @@
|
|||
diff --git a/dist/index.d.mts b/dist/index.d.mts
|
||||
index 1bde0b9f8cbe6771a52c1041095c9dddfe8e5b6c..0ca2ffb2a0c9327aed5ddcf0004500dc8b42569f 100644
|
||||
--- a/dist/index.d.mts
|
||||
+++ b/dist/index.d.mts
|
||||
@@ -14,6 +14,7 @@ declare const mistralLanguageModelOptions: z.ZodObject<{
|
||||
none: "none";
|
||||
high: "high";
|
||||
}>>;
|
||||
+ promptCacheKey: z.ZodOptional<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
type MistralLanguageModelOptions = z.infer<typeof mistralLanguageModelOptions>;
|
||||
|
||||
diff --git a/dist/index.d.ts b/dist/index.d.ts
|
||||
index 1bde0b9f8cbe6771a52c1041095c9dddfe8e5b6c..0ca2ffb2a0c9327aed5ddcf0004500dc8b42569f 100644
|
||||
--- a/dist/index.d.ts
|
||||
+++ b/dist/index.d.ts
|
||||
@@ -14,6 +14,7 @@ declare const mistralLanguageModelOptions: z.ZodObject<{
|
||||
none: "none";
|
||||
high: "high";
|
||||
}>>;
|
||||
+ promptCacheKey: z.ZodOptional<z.ZodString>;
|
||||
}, z.core.$strip>;
|
||||
type MistralLanguageModelOptions = z.infer<typeof mistralLanguageModelOptions>;
|
||||
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index d3f904c12a1d582cc7b9e9a2d30273e1a8505b28..267f34e20ea392b7a85ad5259d72d50605a6f971 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -128,11 +128,14 @@ function convertToMistralChatMessages(prompt) {
|
||||
}
|
||||
case "assistant": {
|
||||
let text = "";
|
||||
+ const structuredContent = [];
|
||||
+ let hasNativeReasoning = false;
|
||||
const toolCalls = [];
|
||||
for (const part of content) {
|
||||
switch (part.type) {
|
||||
case "text": {
|
||||
text += part.text;
|
||||
+ structuredContent.push({ type: "text", text: part.text });
|
||||
break;
|
||||
}
|
||||
case "tool-call": {
|
||||
@@ -148,6 +151,13 @@ function convertToMistralChatMessages(prompt) {
|
||||
}
|
||||
case "reasoning": {
|
||||
text += part.text;
|
||||
+ const native = part.providerOptions?.mistral?.thinking;
|
||||
+ if (native?.type === "thinking") {
|
||||
+ hasNativeReasoning = true;
|
||||
+ structuredContent.push(native);
|
||||
+ break;
|
||||
+ }
|
||||
+ structuredContent.push({ type: "text", text: part.text });
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -159,7 +169,7 @@ function convertToMistralChatMessages(prompt) {
|
||||
}
|
||||
messages.push({
|
||||
role: "assistant",
|
||||
- content: text,
|
||||
+ content: hasNativeReasoning ? structuredContent : text,
|
||||
prefix: isLastMessage ? true : void 0,
|
||||
tool_calls: toolCalls.length > 0 ? toolCalls : void 0
|
||||
});
|
||||
@@ -268,7 +278,8 @@ var mistralLanguageModelOptions = import_v4.z.object({
|
||||
* - `'high'`: Enable reasoning
|
||||
* - `'none'`: Disable reasoning
|
||||
*/
|
||||
- reasoningEffort: import_v4.z.enum(["high", "none"]).optional()
|
||||
+ reasoningEffort: import_v4.z.enum(["high", "none"]).optional(),
|
||||
+ promptCacheKey: import_v4.z.string().optional()
|
||||
});
|
||||
|
||||
// src/mistral-error.ts
|
||||
@@ -407,6 +418,7 @@ var MistralChatLanguageModel = class {
|
||||
stop: stopSequences,
|
||||
random_seed: seed,
|
||||
reasoning_effort: options.reasoningEffort,
|
||||
+ prompt_cache_key: options.promptCacheKey,
|
||||
// response format:
|
||||
response_format: (responseFormat == null ? void 0 : responseFormat.type) === "json" ? structuredOutputs && (responseFormat == null ? void 0 : responseFormat.schema) != null ? {
|
||||
type: "json_schema",
|
||||
@@ -465,9 +477,11 @@ var MistralChatLanguageModel = class {
|
||||
for (const part of choice.message.content) {
|
||||
if (part.type === "thinking") {
|
||||
const reasoningText = extractReasoningContent(part.thinking);
|
||||
- if (reasoningText.length > 0) {
|
||||
- content.push({ type: "reasoning", text: reasoningText });
|
||||
- }
|
||||
+ content.push({
|
||||
+ type: "reasoning",
|
||||
+ text: reasoningText,
|
||||
+ providerMetadata: { mistral: { thinking: part } }
|
||||
+ });
|
||||
} else if (part.type === "text") {
|
||||
if (part.text.length > 0) {
|
||||
content.push({ type: "text", text: part.text });
|
||||
@@ -528,6 +542,7 @@ var MistralChatLanguageModel = class {
|
||||
let isFirstChunk = true;
|
||||
let activeText = false;
|
||||
let activeReasoningId = null;
|
||||
+ let activeThinking = null;
|
||||
const generateId2 = this.generateId;
|
||||
return {
|
||||
stream: response.pipeThrough(
|
||||
@@ -561,18 +576,19 @@ var MistralChatLanguageModel = class {
|
||||
for (const part of delta.content) {
|
||||
if (part.type === "thinking") {
|
||||
const reasoningDelta = extractReasoningContent(part.thinking);
|
||||
- if (reasoningDelta.length > 0) {
|
||||
- if (activeReasoningId == null) {
|
||||
- if (activeText) {
|
||||
- controller.enqueue({ type: "text-end", id: "0" });
|
||||
- activeText = false;
|
||||
- }
|
||||
- activeReasoningId = generateId2();
|
||||
- controller.enqueue({
|
||||
- type: "reasoning-start",
|
||||
- id: activeReasoningId
|
||||
- });
|
||||
+ activeThinking = mergeThinking(activeThinking, part);
|
||||
+ if (activeReasoningId == null) {
|
||||
+ if (activeText) {
|
||||
+ controller.enqueue({ type: "text-end", id: "0" });
|
||||
+ activeText = false;
|
||||
}
|
||||
+ activeReasoningId = generateId2();
|
||||
+ controller.enqueue({
|
||||
+ type: "reasoning-start",
|
||||
+ id: activeReasoningId
|
||||
+ });
|
||||
+ }
|
||||
+ if (reasoningDelta.length > 0) {
|
||||
controller.enqueue({
|
||||
type: "reasoning-delta",
|
||||
id: activeReasoningId,
|
||||
@@ -587,9 +603,11 @@ var MistralChatLanguageModel = class {
|
||||
if (activeReasoningId != null) {
|
||||
controller.enqueue({
|
||||
type: "reasoning-end",
|
||||
- id: activeReasoningId
|
||||
+ id: activeReasoningId,
|
||||
+ providerMetadata: { mistral: { thinking: activeThinking } }
|
||||
});
|
||||
activeReasoningId = null;
|
||||
+ activeThinking = null;
|
||||
}
|
||||
controller.enqueue({ type: "text-start", id: "0" });
|
||||
activeText = true;
|
||||
@@ -638,7 +656,8 @@ var MistralChatLanguageModel = class {
|
||||
if (activeReasoningId != null) {
|
||||
controller.enqueue({
|
||||
type: "reasoning-end",
|
||||
- id: activeReasoningId
|
||||
+ id: activeReasoningId,
|
||||
+ providerMetadata: { mistral: { thinking: activeThinking } }
|
||||
});
|
||||
}
|
||||
if (activeText) {
|
||||
@@ -660,6 +679,13 @@ var MistralChatLanguageModel = class {
|
||||
function extractReasoningContent(thinking) {
|
||||
return thinking.filter((chunk) => chunk.type === "text").map((chunk) => chunk.text).join("");
|
||||
}
|
||||
+function mergeThinking(current, next) {
|
||||
+ if (current === null) return { ...next, thinking: [...next.thinking] };
|
||||
+ current.thinking.push(...next.thinking);
|
||||
+ if (next.closed !== void 0) current.closed = next.closed;
|
||||
+ if (next.signature !== void 0) current.signature = next.signature;
|
||||
+ return current;
|
||||
+}
|
||||
function extractTextContent(content) {
|
||||
if (typeof content === "string") {
|
||||
return content;
|
||||
@@ -686,6 +712,30 @@ function extractTextContent(content) {
|
||||
}
|
||||
return textContent.length ? textContent.join("") : void 0;
|
||||
}
|
||||
+var mistralThinkingContentSchema = import_v43.z.discriminatedUnion("type", [
|
||||
+ import_v43.z.object({
|
||||
+ type: import_v43.z.literal("text"),
|
||||
+ text: import_v43.z.string()
|
||||
+ }),
|
||||
+ import_v43.z.object({
|
||||
+ type: import_v43.z.literal("tool_reference"),
|
||||
+ tool: import_v43.z.string(),
|
||||
+ title: import_v43.z.string(),
|
||||
+ url: import_v43.z.string().nullish(),
|
||||
+ favicon: import_v43.z.string().nullish(),
|
||||
+ description: import_v43.z.string().nullish()
|
||||
+ }),
|
||||
+ import_v43.z.object({
|
||||
+ type: import_v43.z.literal("reference"),
|
||||
+ reference_ids: import_v43.z.array(import_v43.z.union([import_v43.z.string(), import_v43.z.number().int()]))
|
||||
+ })
|
||||
+]);
|
||||
+var mistralThinkChunkSchema = import_v43.z.object({
|
||||
+ type: import_v43.z.literal("thinking"),
|
||||
+ thinking: import_v43.z.array(mistralThinkingContentSchema),
|
||||
+ closed: import_v43.z.boolean().optional(),
|
||||
+ signature: import_v43.z.string().nullish()
|
||||
+});
|
||||
var mistralContentSchema = import_v43.z.union([
|
||||
import_v43.z.string(),
|
||||
import_v43.z.array(
|
||||
@@ -708,15 +758,7 @@ var mistralContentSchema = import_v43.z.union([
|
||||
type: import_v43.z.literal("reference"),
|
||||
reference_ids: import_v43.z.array(import_v43.z.union([import_v43.z.string(), import_v43.z.number()]))
|
||||
}),
|
||||
- import_v43.z.object({
|
||||
- type: import_v43.z.literal("thinking"),
|
||||
- thinking: import_v43.z.array(
|
||||
- import_v43.z.object({
|
||||
- type: import_v43.z.literal("text"),
|
||||
- text: import_v43.z.string()
|
||||
- })
|
||||
- )
|
||||
- })
|
||||
+ mistralThinkChunkSchema
|
||||
])
|
||||
)
|
||||
]).nullish();
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index d2eff622c1b84a96bdeb4012cb0206a33012a04d..3bff11ddd6136ada45809568828cbc8f2493a42a 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -116,11 +116,14 @@ function convertToMistralChatMessages(prompt) {
|
||||
}
|
||||
case "assistant": {
|
||||
let text = "";
|
||||
+ const structuredContent = [];
|
||||
+ let hasNativeReasoning = false;
|
||||
const toolCalls = [];
|
||||
for (const part of content) {
|
||||
switch (part.type) {
|
||||
case "text": {
|
||||
text += part.text;
|
||||
+ structuredContent.push({ type: "text", text: part.text });
|
||||
break;
|
||||
}
|
||||
case "tool-call": {
|
||||
@@ -136,6 +139,13 @@ function convertToMistralChatMessages(prompt) {
|
||||
}
|
||||
case "reasoning": {
|
||||
text += part.text;
|
||||
+ const native = part.providerOptions?.mistral?.thinking;
|
||||
+ if (native?.type === "thinking") {
|
||||
+ hasNativeReasoning = true;
|
||||
+ structuredContent.push(native);
|
||||
+ break;
|
||||
+ }
|
||||
+ structuredContent.push({ type: "text", text: part.text });
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -147,7 +157,7 @@ function convertToMistralChatMessages(prompt) {
|
||||
}
|
||||
messages.push({
|
||||
role: "assistant",
|
||||
- content: text,
|
||||
+ content: hasNativeReasoning ? structuredContent : text,
|
||||
prefix: isLastMessage ? true : void 0,
|
||||
tool_calls: toolCalls.length > 0 ? toolCalls : void 0
|
||||
});
|
||||
@@ -256,7 +266,8 @@ var mistralLanguageModelOptions = z.object({
|
||||
* - `'high'`: Enable reasoning
|
||||
* - `'none'`: Disable reasoning
|
||||
*/
|
||||
- reasoningEffort: z.enum(["high", "none"]).optional()
|
||||
+ reasoningEffort: z.enum(["high", "none"]).optional(),
|
||||
+ promptCacheKey: z.string().optional()
|
||||
});
|
||||
|
||||
// src/mistral-error.ts
|
||||
@@ -397,6 +408,7 @@ var MistralChatLanguageModel = class {
|
||||
stop: stopSequences,
|
||||
random_seed: seed,
|
||||
reasoning_effort: options.reasoningEffort,
|
||||
+ prompt_cache_key: options.promptCacheKey,
|
||||
// response format:
|
||||
response_format: (responseFormat == null ? void 0 : responseFormat.type) === "json" ? structuredOutputs && (responseFormat == null ? void 0 : responseFormat.schema) != null ? {
|
||||
type: "json_schema",
|
||||
@@ -455,9 +467,11 @@ var MistralChatLanguageModel = class {
|
||||
for (const part of choice.message.content) {
|
||||
if (part.type === "thinking") {
|
||||
const reasoningText = extractReasoningContent(part.thinking);
|
||||
- if (reasoningText.length > 0) {
|
||||
- content.push({ type: "reasoning", text: reasoningText });
|
||||
- }
|
||||
+ content.push({
|
||||
+ type: "reasoning",
|
||||
+ text: reasoningText,
|
||||
+ providerMetadata: { mistral: { thinking: part } }
|
||||
+ });
|
||||
} else if (part.type === "text") {
|
||||
if (part.text.length > 0) {
|
||||
content.push({ type: "text", text: part.text });
|
||||
@@ -518,6 +532,7 @@ var MistralChatLanguageModel = class {
|
||||
let isFirstChunk = true;
|
||||
let activeText = false;
|
||||
let activeReasoningId = null;
|
||||
+ let activeThinking = null;
|
||||
const generateId2 = this.generateId;
|
||||
return {
|
||||
stream: response.pipeThrough(
|
||||
@@ -551,18 +566,19 @@ var MistralChatLanguageModel = class {
|
||||
for (const part of delta.content) {
|
||||
if (part.type === "thinking") {
|
||||
const reasoningDelta = extractReasoningContent(part.thinking);
|
||||
- if (reasoningDelta.length > 0) {
|
||||
- if (activeReasoningId == null) {
|
||||
- if (activeText) {
|
||||
- controller.enqueue({ type: "text-end", id: "0" });
|
||||
- activeText = false;
|
||||
- }
|
||||
- activeReasoningId = generateId2();
|
||||
- controller.enqueue({
|
||||
- type: "reasoning-start",
|
||||
- id: activeReasoningId
|
||||
- });
|
||||
+ activeThinking = mergeThinking(activeThinking, part);
|
||||
+ if (activeReasoningId == null) {
|
||||
+ if (activeText) {
|
||||
+ controller.enqueue({ type: "text-end", id: "0" });
|
||||
+ activeText = false;
|
||||
}
|
||||
+ activeReasoningId = generateId2();
|
||||
+ controller.enqueue({
|
||||
+ type: "reasoning-start",
|
||||
+ id: activeReasoningId
|
||||
+ });
|
||||
+ }
|
||||
+ if (reasoningDelta.length > 0) {
|
||||
controller.enqueue({
|
||||
type: "reasoning-delta",
|
||||
id: activeReasoningId,
|
||||
@@ -577,9 +593,11 @@ var MistralChatLanguageModel = class {
|
||||
if (activeReasoningId != null) {
|
||||
controller.enqueue({
|
||||
type: "reasoning-end",
|
||||
- id: activeReasoningId
|
||||
+ id: activeReasoningId,
|
||||
+ providerMetadata: { mistral: { thinking: activeThinking } }
|
||||
});
|
||||
activeReasoningId = null;
|
||||
+ activeThinking = null;
|
||||
}
|
||||
controller.enqueue({ type: "text-start", id: "0" });
|
||||
activeText = true;
|
||||
@@ -628,7 +646,8 @@ var MistralChatLanguageModel = class {
|
||||
if (activeReasoningId != null) {
|
||||
controller.enqueue({
|
||||
type: "reasoning-end",
|
||||
- id: activeReasoningId
|
||||
+ id: activeReasoningId,
|
||||
+ providerMetadata: { mistral: { thinking: activeThinking } }
|
||||
});
|
||||
}
|
||||
if (activeText) {
|
||||
@@ -650,6 +669,13 @@ var MistralChatLanguageModel = class {
|
||||
function extractReasoningContent(thinking) {
|
||||
return thinking.filter((chunk) => chunk.type === "text").map((chunk) => chunk.text).join("");
|
||||
}
|
||||
+function mergeThinking(current, next) {
|
||||
+ if (current === null) return { ...next, thinking: [...next.thinking] };
|
||||
+ current.thinking.push(...next.thinking);
|
||||
+ if (next.closed !== void 0) current.closed = next.closed;
|
||||
+ if (next.signature !== void 0) current.signature = next.signature;
|
||||
+ return current;
|
||||
+}
|
||||
function extractTextContent(content) {
|
||||
if (typeof content === "string") {
|
||||
return content;
|
||||
@@ -676,6 +702,30 @@ function extractTextContent(content) {
|
||||
}
|
||||
return textContent.length ? textContent.join("") : void 0;
|
||||
}
|
||||
+var mistralThinkingContentSchema = z3.discriminatedUnion("type", [
|
||||
+ z3.object({
|
||||
+ type: z3.literal("text"),
|
||||
+ text: z3.string()
|
||||
+ }),
|
||||
+ z3.object({
|
||||
+ type: z3.literal("tool_reference"),
|
||||
+ tool: z3.string(),
|
||||
+ title: z3.string(),
|
||||
+ url: z3.string().nullish(),
|
||||
+ favicon: z3.string().nullish(),
|
||||
+ description: z3.string().nullish()
|
||||
+ }),
|
||||
+ z3.object({
|
||||
+ type: z3.literal("reference"),
|
||||
+ reference_ids: z3.array(z3.union([z3.string(), z3.number().int()]))
|
||||
+ })
|
||||
+]);
|
||||
+var mistralThinkChunkSchema = z3.object({
|
||||
+ type: z3.literal("thinking"),
|
||||
+ thinking: z3.array(mistralThinkingContentSchema),
|
||||
+ closed: z3.boolean().optional(),
|
||||
+ signature: z3.string().nullish()
|
||||
+});
|
||||
var mistralContentSchema = z3.union([
|
||||
z3.string(),
|
||||
z3.array(
|
||||
@@ -698,15 +748,7 @@ var mistralContentSchema = z3.union([
|
||||
type: z3.literal("reference"),
|
||||
reference_ids: z3.array(z3.union([z3.string(), z3.number()]))
|
||||
}),
|
||||
- z3.object({
|
||||
- type: z3.literal("thinking"),
|
||||
- thinking: z3.array(
|
||||
- z3.object({
|
||||
- type: z3.literal("text"),
|
||||
- text: z3.string()
|
||||
- })
|
||||
- )
|
||||
- })
|
||||
+ mistralThinkChunkSchema
|
||||
])
|
||||
)
|
||||
]).nullish();
|
||||
diff --git a/src/convert-to-mistral-chat-messages.ts b/src/convert-to-mistral-chat-messages.ts
|
||||
index 3c6914f8da615d7517bc43dd56198298d0a50247..8cd6f4c7577f746ef41e8a0aee682234c473667a 100644
|
||||
--- a/src/convert-to-mistral-chat-messages.ts
|
||||
+++ b/src/convert-to-mistral-chat-messages.ts
|
||||
@@ -3,7 +3,11 @@ import {
|
||||
type LanguageModelV3DataContent,
|
||||
type LanguageModelV3Prompt,
|
||||
} from '@ai-sdk/provider';
|
||||
-import type { MistralPrompt } from './mistral-chat-prompt';
|
||||
+import type {
|
||||
+ MistralAssistantMessageContent,
|
||||
+ MistralPrompt,
|
||||
+ MistralThinkChunk,
|
||||
+} from './mistral-chat-prompt';
|
||||
import { convertToBase64 } from '@ai-sdk/provider-utils';
|
||||
|
||||
function formatFileUrl({
|
||||
@@ -76,6 +80,8 @@ export function convertToMistralChatMessages(
|
||||
|
||||
case 'assistant': {
|
||||
let text = '';
|
||||
+ const structuredContent: Array<MistralAssistantMessageContent> = [];
|
||||
+ let hasNativeReasoning = false;
|
||||
const toolCalls: Array<{
|
||||
id: string;
|
||||
type: 'function';
|
||||
@@ -86,6 +92,7 @@ export function convertToMistralChatMessages(
|
||||
switch (part.type) {
|
||||
case 'text': {
|
||||
text += part.text;
|
||||
+ structuredContent.push({ type: 'text', text: part.text });
|
||||
break;
|
||||
}
|
||||
case 'tool-call': {
|
||||
@@ -101,6 +108,14 @@ export function convertToMistralChatMessages(
|
||||
}
|
||||
case 'reasoning': {
|
||||
text += part.text;
|
||||
+ const native = part.providerOptions?.mistral
|
||||
+ ?.thinking as MistralThinkChunk | undefined;
|
||||
+ if (native?.type === 'thinking') {
|
||||
+ hasNativeReasoning = true;
|
||||
+ structuredContent.push(native);
|
||||
+ break;
|
||||
+ }
|
||||
+ structuredContent.push({ type: 'text', text: part.text });
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -113,7 +128,7 @@ export function convertToMistralChatMessages(
|
||||
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
- content: text,
|
||||
+ content: hasNativeReasoning ? structuredContent : text,
|
||||
prefix: isLastMessage ? true : undefined,
|
||||
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
||||
});
|
||||
diff --git a/src/mistral-chat-language-model.ts b/src/mistral-chat-language-model.ts
|
||||
index 7e4a7ab552f1b41b7074e1b3cada8a51d791268d..847d26f9dfe03572a969a122f8c96b8bbfda8066 100644
|
||||
--- a/src/mistral-chat-language-model.ts
|
||||
+++ b/src/mistral-chat-language-model.ts
|
||||
@@ -122,6 +122,7 @@ export class MistralChatLanguageModel implements LanguageModelV3 {
|
||||
stop: stopSequences,
|
||||
random_seed: seed,
|
||||
reasoning_effort: options.reasoningEffort,
|
||||
+ prompt_cache_key: options.promptCacheKey,
|
||||
|
||||
// response format:
|
||||
response_format:
|
||||
@@ -201,9 +202,11 @@ export class MistralChatLanguageModel implements LanguageModelV3 {
|
||||
for (const part of choice.message.content) {
|
||||
if (part.type === 'thinking') {
|
||||
const reasoningText = extractReasoningContent(part.thinking);
|
||||
- if (reasoningText.length > 0) {
|
||||
- content.push({ type: 'reasoning', text: reasoningText });
|
||||
- }
|
||||
+ content.push({
|
||||
+ type: 'reasoning',
|
||||
+ text: reasoningText,
|
||||
+ providerMetadata: { mistral: { thinking: part } },
|
||||
+ });
|
||||
} else if (part.type === 'text') {
|
||||
if (part.text.length > 0) {
|
||||
content.push({ type: 'text', text: part.text });
|
||||
@@ -278,6 +281,7 @@ export class MistralChatLanguageModel implements LanguageModelV3 {
|
||||
let isFirstChunk = true;
|
||||
let activeText = false;
|
||||
let activeReasoningId: string | null = null;
|
||||
+ let activeThinking: z.infer<typeof mistralThinkChunkSchema> | null = null;
|
||||
|
||||
const generateId = this.generateId;
|
||||
|
||||
@@ -326,20 +330,21 @@ export class MistralChatLanguageModel implements LanguageModelV3 {
|
||||
for (const part of delta.content) {
|
||||
if (part.type === 'thinking') {
|
||||
const reasoningDelta = extractReasoningContent(part.thinking);
|
||||
- if (reasoningDelta.length > 0) {
|
||||
- if (activeReasoningId == null) {
|
||||
- // end any active text before starting reasoning
|
||||
- if (activeText) {
|
||||
- controller.enqueue({ type: 'text-end', id: '0' });
|
||||
- activeText = false;
|
||||
- }
|
||||
-
|
||||
- activeReasoningId = generateId();
|
||||
- controller.enqueue({
|
||||
- type: 'reasoning-start',
|
||||
- id: activeReasoningId,
|
||||
- });
|
||||
+ activeThinking = mergeThinking(activeThinking, part);
|
||||
+ if (activeReasoningId == null) {
|
||||
+ // end any active text before starting reasoning
|
||||
+ if (activeText) {
|
||||
+ controller.enqueue({ type: 'text-end', id: '0' });
|
||||
+ activeText = false;
|
||||
}
|
||||
+
|
||||
+ activeReasoningId = generateId();
|
||||
+ controller.enqueue({
|
||||
+ type: 'reasoning-start',
|
||||
+ id: activeReasoningId,
|
||||
+ });
|
||||
+ }
|
||||
+ if (reasoningDelta.length > 0) {
|
||||
controller.enqueue({
|
||||
type: 'reasoning-delta',
|
||||
id: activeReasoningId,
|
||||
@@ -357,8 +362,12 @@ export class MistralChatLanguageModel implements LanguageModelV3 {
|
||||
controller.enqueue({
|
||||
type: 'reasoning-end',
|
||||
id: activeReasoningId,
|
||||
+ providerMetadata: {
|
||||
+ mistral: { thinking: activeThinking },
|
||||
+ },
|
||||
});
|
||||
activeReasoningId = null;
|
||||
+ activeThinking = null;
|
||||
}
|
||||
controller.enqueue({ type: 'text-start', id: '0' });
|
||||
activeText = true;
|
||||
@@ -416,6 +425,9 @@ export class MistralChatLanguageModel implements LanguageModelV3 {
|
||||
controller.enqueue({
|
||||
type: 'reasoning-end',
|
||||
id: activeReasoningId,
|
||||
+ providerMetadata: {
|
||||
+ mistral: { thinking: activeThinking },
|
||||
+ },
|
||||
});
|
||||
}
|
||||
if (activeText) {
|
||||
@@ -437,7 +449,7 @@ export class MistralChatLanguageModel implements LanguageModelV3 {
|
||||
}
|
||||
|
||||
function extractReasoningContent(
|
||||
- thinking: Array<{ type: string; text: string }>,
|
||||
+ thinking: Array<z.infer<typeof mistralThinkingContentSchema>>,
|
||||
) {
|
||||
return thinking
|
||||
.filter(chunk => chunk.type === 'text')
|
||||
@@ -445,6 +457,17 @@ function extractReasoningContent(
|
||||
.join('');
|
||||
}
|
||||
|
||||
+function mergeThinking(
|
||||
+ current: z.infer<typeof mistralThinkChunkSchema> | null,
|
||||
+ next: z.infer<typeof mistralThinkChunkSchema>,
|
||||
+) {
|
||||
+ if (current === null) return { ...next, thinking: [...next.thinking] };
|
||||
+ current.thinking.push(...next.thinking);
|
||||
+ if (next.closed !== undefined) current.closed = next.closed;
|
||||
+ if (next.signature !== undefined) current.signature = next.signature;
|
||||
+ return current;
|
||||
+}
|
||||
+
|
||||
function extractTextContent(content: z.infer<typeof mistralContentSchema>) {
|
||||
if (typeof content === 'string') {
|
||||
return content;
|
||||
@@ -478,6 +501,32 @@ function extractTextContent(content: z.infer<typeof mistralContentSchema>) {
|
||||
return textContent.length ? textContent.join('') : undefined;
|
||||
}
|
||||
|
||||
+const mistralThinkingContentSchema = z.discriminatedUnion('type', [
|
||||
+ z.object({
|
||||
+ type: z.literal('text'),
|
||||
+ text: z.string(),
|
||||
+ }),
|
||||
+ z.object({
|
||||
+ type: z.literal('tool_reference'),
|
||||
+ tool: z.string(),
|
||||
+ title: z.string(),
|
||||
+ url: z.string().nullish(),
|
||||
+ favicon: z.string().nullish(),
|
||||
+ description: z.string().nullish(),
|
||||
+ }),
|
||||
+ z.object({
|
||||
+ type: z.literal('reference'),
|
||||
+ reference_ids: z.array(z.union([z.string(), z.number().int()])),
|
||||
+ }),
|
||||
+]);
|
||||
+
|
||||
+const mistralThinkChunkSchema = z.object({
|
||||
+ type: z.literal('thinking'),
|
||||
+ thinking: z.array(mistralThinkingContentSchema),
|
||||
+ closed: z.boolean().optional(),
|
||||
+ signature: z.string().nullish(),
|
||||
+});
|
||||
+
|
||||
const mistralContentSchema = z
|
||||
.union([
|
||||
z.string(),
|
||||
@@ -501,15 +550,7 @@ const mistralContentSchema = z
|
||||
type: z.literal('reference'),
|
||||
reference_ids: z.array(z.union([z.string(), z.number()])),
|
||||
}),
|
||||
- z.object({
|
||||
- type: z.literal('thinking'),
|
||||
- thinking: z.array(
|
||||
- z.object({
|
||||
- type: z.literal('text'),
|
||||
- text: z.string(),
|
||||
- }),
|
||||
- ),
|
||||
- }),
|
||||
+ mistralThinkChunkSchema,
|
||||
]),
|
||||
),
|
||||
])
|
||||
diff --git a/src/mistral-chat-options.ts b/src/mistral-chat-options.ts
|
||||
index 54b29c08517d348995b6ca093b11160e453d5c8b..de30c3e7d924889339e38b1067cb26e9ada05d11 100644
|
||||
--- a/src/mistral-chat-options.ts
|
||||
+++ b/src/mistral-chat-options.ts
|
||||
@@ -64,6 +64,11 @@ export const mistralLanguageModelOptions = z.object({
|
||||
* - `'none'`: Disable reasoning
|
||||
*/
|
||||
reasoningEffort: z.enum(['high', 'none']).optional(),
|
||||
+
|
||||
+ /**
|
||||
+ * A stable identifier used to route requests with shared prompt prefixes.
|
||||
+ */
|
||||
+ promptCacheKey: z.string().optional(),
|
||||
});
|
||||
|
||||
export type MistralLanguageModelOptions = z.infer<
|
||||
diff --git a/src/mistral-chat-prompt.ts b/src/mistral-chat-prompt.ts
|
||||
index 13f1dced55ac4be084128127a57fbdd58115bc28..172b11dde3dd326c2f3befd99237474ed8c79285 100644
|
||||
--- a/src/mistral-chat-prompt.ts
|
||||
+++ b/src/mistral-chat-prompt.ts
|
||||
@@ -23,7 +23,7 @@ export type MistralUserMessageContent =
|
||||
|
||||
export interface MistralAssistantMessage {
|
||||
role: 'assistant';
|
||||
- content: string;
|
||||
+ content: string | Array<MistralAssistantMessageContent>;
|
||||
prefix?: boolean;
|
||||
tool_calls?: Array<{
|
||||
id: string;
|
||||
@@ -32,6 +32,29 @@ export interface MistralAssistantMessage {
|
||||
}>;
|
||||
}
|
||||
|
||||
+export type MistralAssistantMessageContent =
|
||||
+ | { type: 'text'; text: string }
|
||||
+ | MistralThinkChunk;
|
||||
+
|
||||
+export type MistralThinkChunk = {
|
||||
+ type: 'thinking';
|
||||
+ thinking: Array<MistralThinkingContent>;
|
||||
+ closed?: boolean;
|
||||
+ signature?: string | null;
|
||||
+};
|
||||
+
|
||||
+export type MistralThinkingContent =
|
||||
+ | { type: 'text'; text: string }
|
||||
+ | {
|
||||
+ type: 'tool_reference';
|
||||
+ tool: string;
|
||||
+ title: string;
|
||||
+ url?: string | null;
|
||||
+ favicon?: string | null;
|
||||
+ description?: string | null;
|
||||
+ }
|
||||
+ | { type: 'reference'; reference_ids: Array<string | number> };
|
||||
+
|
||||
export interface MistralToolMessage {
|
||||
role: 'tool';
|
||||
name: string;
|
||||
Loading…
Add table
Add a link
Reference in a new issue