From b3d16aa62a1b8724fe57ab3846a83f03a4a3fbc0 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Tue, 19 May 2026 20:44:09 +0900 Subject: fix: DeepSeek reasoning_content dropped on multi-step tool calls - Base URL corrected: zen/v1 -> zen/go/v1 (opencode-go provider) - Model changed: deepseek-v4-flash-free -> deepseek-v4-flash - Added wrapLanguageModel middleware to inject reasoning_content via providerMetadata.openaiCompatible before each stream call - Fixed test mocks: removed vi.importActual (unsupported in Bun), added tool factory mocks, preserved real tool export in ai mock - Added 11 tests for the normalizeMessages middleware --- packages/api/tests/agent-manager.test.ts | 64 ++++++++++++++++++++----------- packages/api/tests/routes.test.ts | 65 +++++++++++++++++++++----------- 2 files changed, 85 insertions(+), 44 deletions(-) (limited to 'packages/api/tests') diff --git a/packages/api/tests/agent-manager.test.ts b/packages/api/tests/agent-manager.test.ts index 17b0bff..56cb818 100644 --- a/packages/api/tests/agent-manager.test.ts +++ b/packages/api/tests/agent-manager.test.ts @@ -1,28 +1,48 @@ -import type { AgentEvent } from "@dispatch/core"; +import type { AgentEvent, ToolDefinition } from "@dispatch/core"; import { describe, expect, it, vi } from "vitest"; // Mock @dispatch/core's Agent to avoid real LLM calls -vi.mock("@dispatch/core", async () => { - const actual = await vi.importActual("@dispatch/core"); - return { - ...actual, - Agent: class MockAgent { - status = "idle"; - messages: unknown[] = []; - async *run(_message: string) { - yield { type: "status", status: "running" } as const; - await new Promise((r) => setTimeout(r, 10)); - yield { type: "text-delta", delta: "Hello " } as const; - yield { type: "text-delta", delta: "world" } as const; - yield { - type: "done", - message: { role: "assistant", content: "Hello world" }, - } as const; - yield { type: "status", status: "idle" } as const; - } - }, - }; -}); +vi.mock("@dispatch/core", () => ({ + Agent: class MockAgent { + status = "idle"; + messages: unknown[] = []; + async *run(_message: string) { + yield { type: "status", status: "running" } as const; + await new Promise((r) => setTimeout(r, 10)); + yield { type: "text-delta", delta: "Hello " } as const; + yield { type: "text-delta", delta: "world" } as const; + yield { + type: "done", + message: { role: "assistant", content: "Hello world" }, + } as const; + yield { type: "status", status: "idle" } as const; + } + }, + createReadFileTool(_wd: string): ToolDefinition { + return { + name: "read_file", + description: "read a file", + parameters: { _type: "z.ZodObject", shape: {} } as unknown as ToolDefinition["parameters"], + execute: async () => "mock file content", + }; + }, + createWriteFileTool(_wd: string): ToolDefinition { + return { + name: "write_file", + description: "write a file", + parameters: { _type: "z.ZodObject", shape: {} } as unknown as ToolDefinition["parameters"], + execute: async () => true, + }; + }, + createListFilesTool(_wd: string): ToolDefinition { + return { + name: "list_files", + description: "list files", + parameters: { _type: "z.ZodObject", shape: {} } as unknown as ToolDefinition["parameters"], + execute: async () => ["file1.ts"], + }; + }, +})); // Import after mock is defined (Vitest hoists vi.mock automatically) const { AgentManager } = await import("../src/agent-manager.js"); diff --git a/packages/api/tests/routes.test.ts b/packages/api/tests/routes.test.ts index d5384b3..9f852ee 100644 --- a/packages/api/tests/routes.test.ts +++ b/packages/api/tests/routes.test.ts @@ -1,28 +1,49 @@ +import type { ToolDefinition } from "@dispatch/core"; import { describe, expect, it, vi } from "vitest"; // Mock @dispatch/core's Agent to avoid real LLM calls -vi.mock("@dispatch/core", async () => { - const actual = await vi.importActual("@dispatch/core"); - return { - ...actual, - Agent: class MockAgent { - status = "idle"; - messages: unknown[] = []; - async *run(_message: string) { - yield { type: "status", status: "running" } as const; - // Simulate some processing time so status stays "running" - await new Promise((r) => setTimeout(r, 100)); - yield { type: "text-delta", delta: "Hello " } as const; - yield { type: "text-delta", delta: "world" } as const; - yield { - type: "done", - message: { role: "assistant", content: "Hello world" }, - } as const; - yield { type: "status", status: "idle" } as const; - } - }, - }; -}); +vi.mock("@dispatch/core", () => ({ + Agent: class MockAgent { + status = "idle"; + messages: unknown[] = []; + async *run(_message: string) { + yield { type: "status", status: "running" } as const; + // Simulate some processing time so status stays "running" + await new Promise((r) => setTimeout(r, 100)); + yield { type: "text-delta", delta: "Hello " } as const; + yield { type: "text-delta", delta: "world" } as const; + yield { + type: "done", + message: { role: "assistant", content: "Hello world" }, + } as const; + yield { type: "status", status: "idle" } as const; + } + }, + createReadFileTool(_wd: string): ToolDefinition { + return { + name: "read_file", + description: "read a file", + parameters: { _type: "z.ZodObject", shape: {} } as unknown as ToolDefinition["parameters"], + execute: async () => "mock file content", + }; + }, + createWriteFileTool(_wd: string): ToolDefinition { + return { + name: "write_file", + description: "write a file", + parameters: { _type: "z.ZodObject", shape: {} } as unknown as ToolDefinition["parameters"], + execute: async () => true, + }; + }, + createListFilesTool(_wd: string): ToolDefinition { + return { + name: "list_files", + description: "list files", + parameters: { _type: "z.ZodObject", shape: {} } as unknown as ToolDefinition["parameters"], + execute: async () => ["file1.ts"], + }; + }, +})); const { app } = await import("../src/app.js"); -- cgit v1.2.3