diff --git a/src/api/providers/__tests__/nanogpt.spec.ts b/src/api/providers/__tests__/nanogpt.spec.ts
index 6ac9a17d61..f8d6e61241 100644
--- a/src/api/providers/__tests__/nanogpt.spec.ts
+++ b/src/api/providers/__tests__/nanogpt.spec.ts
@@ -162,6 +162,78 @@ describe("NanoGptHandler", () => {
expect(mockCreate.mock.calls[0][0]).not.toHaveProperty("max_completion_tokens")
})
+ it("keeps Muse Spark tool-result history contiguous across turns", 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" } } },
+ ]
+ 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", toolHistory, {
+ taskId: "task",
+ tools,
+ tool_choice: "auto",
+ parallelToolCalls: true,
+ }),
+ )
+
+ 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: true,
+ }),
+ 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..7b0c7930d3 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_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]
@@ -93,7 +95,12 @@ 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,