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 } : {}), }