From ec5f8fd41c36a20171c9723f3913c37f405297b0 Mon Sep 17 00:00:00 2001
From: "@navedmerchant" <14171946+navedmerchant@users.noreply.github.com>
Date: Thu, 20 Aug 2026 20:50:53 +0000
Subject: [PATCH 1/2] fix(nanogpt): serialize Muse Spark tool calls
---
src/api/providers/__tests__/nanogpt.spec.ts | 33 +++++++++++++++++++++
src/api/providers/nanogpt.ts | 6 +++-
2 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/src/api/providers/__tests__/nanogpt.spec.ts b/src/api/providers/__tests__/nanogpt.spec.ts
index 6ac9a17d61..0a4a4445d6 100644
--- a/src/api/providers/__tests__/nanogpt.spec.ts
+++ b/src/api/providers/__tests__/nanogpt.spec.ts
@@ -162,6 +162,39 @@ describe("NanoGptHandler", () => {
expect(mockCreate.mock.calls[0][0]).not.toHaveProperty("max_completion_tokens")
})
+ it("uses sequential tool calls for Muse Spark 1.2 Contributor", async () => {
+ const modelId = "meta/muse-spark-1.2-contributor"
+ vi.mocked(getModels).mockResolvedValue({
+ [modelId]: {
+ maxTokens: 65_536,
+ contextWindow: 1_000_000,
+ supportsPromptCache: false,
+ },
+ })
+ const tools: OpenAI.Chat.ChatCompletionTool[] = [
+ { type: "function", function: { name: "read_file", parameters: { type: "object" } } },
+ ]
+
+ await collectStream(
+ new NanoGptHandler({ nanoGptModelId: modelId }).createMessage("sys", messages, {
+ taskId: "task",
+ tools,
+ tool_choice: "auto",
+ parallelToolCalls: true,
+ }),
+ )
+
+ expect(mockCreate).toHaveBeenCalledWith(
+ expect.objectContaining({
+ model: modelId,
+ tools: [expect.objectContaining({ function: expect.objectContaining({ name: "read_file" }) })],
+ tool_choice: "auto",
+ parallel_tool_calls: false,
+ }),
+ expect.anything(),
+ )
+ })
+
it("omits temperature when it was not explicitly configured", async () => {
await collectStream(new NanoGptHandler({ nanoGptModelId: "model:thinking" }).createMessage("sys", messages))
expect(mockCreate.mock.calls[0][0]).not.toHaveProperty("temperature")
diff --git a/src/api/providers/nanogpt.ts b/src/api/providers/nanogpt.ts
index 38fb54c384..8515a69526 100644
--- a/src/api/providers/nanogpt.ts
+++ b/src/api/providers/nanogpt.ts
@@ -27,6 +27,8 @@ type NanoGptUsage = OpenAI.CompletionUsage & {
type NanoGptCachingRequest = { caching?: true }
+const NANO_GPT_SEQUENTIAL_TOOL_CALL_MODELS = new Set(["meta/muse-spark-1.2-contributor"])
+
const OPENAI_REASONING_EFFORTS = ["low", "medium", "high"] as const
type OpenAiReasoningEffort = (typeof OPENAI_REASONING_EFFORTS)[number]
@@ -99,7 +101,9 @@ export class NanoGptHandler extends RouterProvider implements SingleCompletionHa
max_tokens: info.maxTokens ?? undefined,
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
- parallel_tool_calls: metadata?.parallelToolCalls ?? true,
+ parallel_tool_calls: NANO_GPT_SEQUENTIAL_TOOL_CALL_MODELS.has(canonicalModelId)
+ ? false
+ : (metadata?.parallelToolCalls ?? true),
...(this.options.nanoGptRoutingPreference === "caching" ? { caching: true } : {}),
}
From 5f2b862d5ebf7383ba26648a2106ddea37f824b3 Mon Sep 17 00:00:00 2001
From: "@navedmerchant" <14171946+navedmerchant@users.noreply.github.com>
Date: Thu, 20 Aug 2026 23:25:31 +0000
Subject: [PATCH 2/2] fix(nanogpt): preserve Muse tool-result context
---
src/api/providers/__tests__/nanogpt.spec.ts | 45 +++++++++++++++++++--
src/api/providers/nanogpt.ts | 13 +++---
2 files changed, 50 insertions(+), 8 deletions(-)
diff --git a/src/api/providers/__tests__/nanogpt.spec.ts b/src/api/providers/__tests__/nanogpt.spec.ts
index 0a4a4445d6..f8d6e61241 100644
--- a/src/api/providers/__tests__/nanogpt.spec.ts
+++ b/src/api/providers/__tests__/nanogpt.spec.ts
@@ -162,7 +162,7 @@ describe("NanoGptHandler", () => {
expect(mockCreate.mock.calls[0][0]).not.toHaveProperty("max_completion_tokens")
})
- it("uses sequential tool calls for Muse Spark 1.2 Contributor", async () => {
+ it("keeps Muse Spark tool-result history contiguous across turns", async () => {
const modelId = "meta/muse-spark-1.2-contributor"
vi.mocked(getModels).mockResolvedValue({
[modelId]: {
@@ -174,9 +174,33 @@ describe("NanoGptHandler", () => {
const tools: OpenAI.Chat.ChatCompletionTool[] = [
{ type: "function", function: { name: "read_file", parameters: { type: "object" } } },
]
+ const toolHistory: Anthropic.Messages.MessageParam[] = [
+ {
+ role: "assistant",
+ content: [{ type: "tool_use", id: "call_1", name: "read_file", input: { path: "first.txt" } }],
+ },
+ {
+ role: "user",
+ content: [
+ { type: "tool_result", tool_use_id: "call_1", content: "first result" },
+ { type: "text", text: "first context" },
+ ],
+ },
+ {
+ role: "assistant",
+ content: [{ type: "tool_use", id: "call_2", name: "read_file", input: { path: "second.txt" } }],
+ },
+ {
+ role: "user",
+ content: [
+ { type: "tool_result", tool_use_id: "call_2", content: "second result" },
+ { type: "text", text: "second context" },
+ ],
+ },
+ ]
await collectStream(
- new NanoGptHandler({ nanoGptModelId: modelId }).createMessage("sys", messages, {
+ new NanoGptHandler({ nanoGptModelId: modelId }).createMessage("sys", toolHistory, {
taskId: "task",
tools,
tool_choice: "auto",
@@ -187,9 +211,24 @@ describe("NanoGptHandler", () => {
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: modelId,
+ messages: [
+ { role: "system", content: "sys" },
+ expect.objectContaining({ role: "assistant", tool_calls: [expect.anything()] }),
+ {
+ role: "tool",
+ tool_call_id: "call_1",
+ content: "first result\n\nfirst context",
+ },
+ expect.objectContaining({ role: "assistant", tool_calls: [expect.anything()] }),
+ {
+ role: "tool",
+ tool_call_id: "call_2",
+ content: "second result\n\nsecond context",
+ },
+ ],
tools: [expect.objectContaining({ function: expect.objectContaining({ name: "read_file" }) })],
tool_choice: "auto",
- parallel_tool_calls: false,
+ parallel_tool_calls: true,
}),
expect.anything(),
)
diff --git a/src/api/providers/nanogpt.ts b/src/api/providers/nanogpt.ts
index 8515a69526..7b0c7930d3 100644
--- a/src/api/providers/nanogpt.ts
+++ b/src/api/providers/nanogpt.ts
@@ -27,7 +27,7 @@ type NanoGptUsage = OpenAI.CompletionUsage & {
type NanoGptCachingRequest = { caching?: true }
-const NANO_GPT_SEQUENTIAL_TOOL_CALL_MODELS = new Set(["meta/muse-spark-1.2-contributor"])
+const NANO_GPT_MERGED_TOOL_RESULT_MODELS = new Set(["meta/muse-spark-1.2-contributor"])
const OPENAI_REASONING_EFFORTS = ["low", "medium", "high"] as const
type OpenAiReasoningEffort = (typeof OPENAI_REASONING_EFFORTS)[number]
@@ -95,15 +95,18 @@ export class NanoGptHandler extends RouterProvider implements SingleCompletionHa
const { id: canonicalModelId, info } = await this.fetchModel()
const body: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming & NanoGptCachingRequest = {
model: this.getRequestModelId(canonicalModelId),
- messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
+ messages: [
+ { role: "system", content: systemPrompt },
+ ...convertToOpenAiMessages(messages, {
+ mergeToolResultText: NANO_GPT_MERGED_TOOL_RESULT_MODELS.has(canonicalModelId),
+ }),
+ ],
stream: true,
stream_options: { include_usage: true },
max_tokens: info.maxTokens ?? undefined,
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
- parallel_tool_calls: NANO_GPT_SEQUENTIAL_TOOL_CALL_MODELS.has(canonicalModelId)
- ? false
- : (metadata?.parallelToolCalls ?? true),
+ parallel_tool_calls: metadata?.parallelToolCalls ?? true,
...(this.options.nanoGptRoutingPreference === "caching" ? { caching: true } : {}),
}