diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index fe8c32f0323..8f3b3c4d523 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -6,17 +6,27 @@ import { Config } from "../config/config" import { Instance } from "../project/instance" import path from "path" import os from "os" +import * as fs from "fs" import PROMPT_ANTHROPIC from "./prompt/anthropic.txt" import PROMPT_ANTHROPIC_WITHOUT_TODO from "./prompt/qwen.txt" import PROMPT_BEAST from "./prompt/beast.txt" import PROMPT_GEMINI from "./prompt/gemini.txt" import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt" - import PROMPT_CODEX from "./prompt/codex.txt" import type { Provider } from "@/provider/provider" import { Flag } from "@/flag/flag" +const PROMPT_CACHE = new Map() + +function getSystemPromptPaths(): string[] { + return [ + process.env.OPENCODE_PROMPTS_DIR, + path.join(Instance.directory, ".opencode", "prompt"), + path.join(Global.Path.config, "prompt"), + ].filter(Boolean) as string[] +} + export namespace SystemPrompt { export function header(providerID: string) { if (providerID.includes("anthropic")) return [PROMPT_ANTHROPIC_SPOOF.trim()] @@ -24,14 +34,44 @@ export namespace SystemPrompt { } export function provider(model: Provider.Model) { - if (model.api.id.includes("gpt-5")) return [PROMPT_CODEX] - if (model.api.id.includes("gpt-") || model.api.id.includes("o1") || model.api.id.includes("o3")) + const providerId = model.api.id + const promptFile = getPromptFile(providerId) + const customPrompt = findCustomPrompt(promptFile) + if (customPrompt) return [customPrompt] + + if (providerId.includes("gpt-5")) return [PROMPT_CODEX] + if (providerId.includes("gpt-") || providerId.includes("o1") || providerId.includes("o3")) return [PROMPT_BEAST] - if (model.api.id.includes("gemini-")) return [PROMPT_GEMINI] - if (model.api.id.includes("claude")) return [PROMPT_ANTHROPIC] + if (providerId.includes("gemini-")) return [PROMPT_GEMINI] + if (providerId.includes("claude")) return [PROMPT_ANTHROPIC] return [PROMPT_ANTHROPIC_WITHOUT_TODO] } + function getPromptFile(modelId: string): string { + if (modelId.includes("claude")) return "anthropic.txt" + if (modelId.includes("gpt-5")) return "codex.txt" + if (modelId.includes("gpt-") || modelId.includes("o1") || modelId.includes("o3")) + return "beast.txt" + if (modelId.includes("gemini-")) return "gemini.txt" + return "qwen.txt" + } + + function findCustomPrompt(promptFile: string): string | undefined { + const searchPaths = getSystemPromptPaths() + for (const searchPath of searchPaths) { + const fullPath = path.join(searchPath, promptFile) + const cached = PROMPT_CACHE.get(fullPath) + if (cached) return cached + + if (fs.existsSync(fullPath)) { + const content = fs.readFileSync(fullPath, "utf-8") + PROMPT_CACHE.set(fullPath, content) + return content + } + } + return undefined + } + export async function environment() { const project = Instance.project return [