From 5fce025c40ab3ff23887b21b77eb1fc0392122e5 Mon Sep 17 00:00:00 2001 From: zhangdw156 Date: Tue, 21 Apr 2026 17:11:07 +0800 Subject: [PATCH] fix(opencode): merge system prompts for non-anthropic providers --- packages/opencode/src/session/llm.ts | 7 +- packages/opencode/test/session/llm.test.ts | 97 ++++++++++++++++++++++ 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index b72f873de01d..0a068ebbdef2 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -150,12 +150,7 @@ const live: Layer.Layer< : isWorkflow ? input.messages : [ - ...system.map( - (x): ModelMessage => ({ - role: "system", - content: x, - }), - ), + { role: "system" as const, content: system.join("\n") } satisfies ModelMessage, ...input.messages, ] diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index 4d82096f3f9a..a37faeeeb500 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -3,6 +3,7 @@ import path from "path" import { tool, type ModelMessage } from "ai" import { Cause, Effect, Exit, Stream } from "effect" import z from "zod" +import { pathToFileURL } from "url" import { makeRuntime } from "../../src/effect/run-service" import { LLM } from "../../src/session/llm" import { Instance } from "../../src/project/instance" @@ -561,6 +562,102 @@ describe("session.llm.stream", () => { }) }) + test("combines plugin-added system prompts into one message for non-anthropic providers", async () => { + const server = state.server + if (!server) { + throw new Error("Server not initialized") + } + + const providerID = "alibaba" + const modelID = "qwen-plus" + const fixture = await loadFixture(providerID, modelID) + const model = fixture.model + + const request = waitRequest( + "/chat/completions", + new Response(createChatStream("Hello"), { + status: 200, + headers: { "Content-Type": "text/event-stream" }, + }), + ) + + await using tmp = await tmpdir({ + init: async (dir) => { + const pluginFile = path.join(dir, "plugin.ts") + await Bun.write( + pluginFile, + [ + "export default async () => ({", + ' "experimental.chat.system.transform": (_input, output) => {', + ' output.system.push("Relevant memory goes here.")', + " },", + "})", + "", + ].join("\n"), + ) + + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + enabled_providers: [providerID], + plugin: [pathToFileURL(pluginFile).href], + provider: { + [providerID]: { + options: { + apiKey: "test-key", + baseURL: `${server.url.origin}/v1`, + }, + }, + }, + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const resolved = await getModel(ProviderID.make(providerID), ModelID.make(model.id)) + const sessionID = SessionID.make("session-test-system-merge") + const agent = { + name: "test", + mode: "primary", + options: {}, + permission: [{ permission: "*", pattern: "*", action: "allow" }], + } satisfies Agent.Info + + const user = { + id: MessageID.make("user-system-merge"), + sessionID, + role: "user", + time: { created: Date.now() }, + agent: agent.name, + model: { providerID: ProviderID.make(providerID), modelID: resolved.id }, + } satisfies MessageV2.User + + await drain({ + user, + sessionID, + model: resolved, + agent, + system: ["You are a helpful assistant."], + messages: [{ role: "user", content: "Hello" }], + tools: {}, + }) + + const capture = await request + const messages = capture.body.messages as Array<{ role: string; content: unknown }> + const systemMessages = messages.filter((message) => message.role === "system") + const systemContent = String(systemMessages[0]?.content ?? "") + + expect(systemMessages).toHaveLength(1) + expect(systemContent).toContain("You are a helpful assistant.") + expect(systemContent).toContain("Relevant memory goes here.") + }, + }) + }) + test("sends responses API payload for OpenAI models", async () => { const server = state.server if (!server) {