summaryrefslogtreecommitdiffhomepage
path: root/packages/api
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-19 20:44:09 +0900
committerAdam Malczewski <[email protected]>2026-05-19 20:44:09 +0900
commitb3d16aa62a1b8724fe57ab3846a83f03a4a3fbc0 (patch)
tree480a1fa2c95bf87ab214f709b1b4414aa64e8929 /packages/api
parentf78a91c20f658dd404277919a0b872b352c99bb6 (diff)
downloaddispatch-b3d16aa62a1b8724fe57ab3846a83f03a4a3fbc0.tar.gz
dispatch-b3d16aa62a1b8724fe57ab3846a83f03a4a3fbc0.zip
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
Diffstat (limited to 'packages/api')
-rw-r--r--packages/api/src/agent-manager.ts4
-rw-r--r--packages/api/tests/agent-manager.test.ts64
-rw-r--r--packages/api/tests/routes.test.ts65
3 files changed, 87 insertions, 46 deletions
diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts
index f60b8d3..2991ee1 100644
--- a/packages/api/src/agent-manager.ts
+++ b/packages/api/src/agent-manager.ts
@@ -24,7 +24,7 @@ export class AgentManager {
private getOrCreateAgent(): Agent {
if (!this.agent) {
const apiKey = process.env.OPENCODE_API_KEY ?? "";
- const model = process.env.DISPATCH_MODEL ?? "deepseek-v4-flash-free";
+ const model = process.env.DISPATCH_MODEL ?? "deepseek-v4-flash";
const workingDirectory = process.env.DISPATCH_WORKING_DIR ?? process.cwd();
const tools = [
@@ -36,7 +36,7 @@ export class AgentManager {
this.agent = new Agent({
model,
apiKey,
- baseURL: "https://opencode.ai/zen/v1",
+ baseURL: "https://opencode.ai/zen/go/v1",
systemPrompt: SYSTEM_PROMPT,
tools,
workingDirectory,
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<typeof import("@dispatch/core")>("@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<void>((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<void>((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<typeof import("@dispatch/core")>("@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<void>((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<void>((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");