通过自制插件修改 OpenCode 系统提示词

通过自制插件修改 OpenCode 系统提示词

OpenCode 暂不支持原生替换系统提示词,可通过 Plugin 利用hook修改实现。

背景

GitHub Issue #7101 提出此功能几个月,不过还没有合并相关功能。我们基于Issue内的社区方案改进,确保向后兼容。

根据 Issue #7101,修改系统提示词的核心原因:

  1. 低上下文模型 - Phi4、gpt-oss 等本地/小型模型,上下文窗口有限(如 128K),超出后会报 context window exceeded
  2. 节省 token - AGENTS.md 是追加而非替换,无法精简整个提示词
  3. 快速迭代 - 不需要重新编译 OpenCode,可随时调整提示词
  4. 系统提示词过大且指令固化 - “extremely large, overly opinionated”
  5. 特定模型需要特定指令 - 自定义模型需要特定格式化说明
  6. 定制化需求 - 用户希望有更多控制权
  7. 与系统提示词"斗争" - “fighting the system prompts time and time again”

前言

你会因为什么原因想要修改系统提示词呢?就我个人而言,主要是第4、6、7条,首先我不觉得自带的系统提示词写得很好,而且大小10kb,性价比比较低,其次,我准备参照iflow的写法来优化一个针对不那么强的模型,因为glm现在因为用户太多了,体验不佳。为什么要用iflow的提示词呢?因为这种写法对国产模型相对友好,我觉得Claude模型很大程度是因为Claude Code这个工具设计得好,所以我也会参考Claude Code来写提示词。

基本上,我希望尽可能地在opencode上还原iflow的那种心流体验,在掌控系统提示词后,还可以开发更多适配国产模型的扩展,或者使用已有的、有用的插件或者配置项目。

说真的,这是我第一个能改系统提示词的工具,我在用那些国产IDE的时候就有一种深深的无力感,在上下文腐败之前就已经抓不住重点,总是急着想要用方案和代码来稳稳接住你

系统提示词在这里,我个人建议使用英文原版的提示词,更加精确和还原体验。
想了好久,只能把iFlow的焚诀炼化出来用一种新的形式陪伴了

友情提示:即便你是自备模型,也不要用opencode处理敏感数据,因为win上编译的版本是exe文件,而不是js文件,你看不到源码,这也是为什么我无法在本地直接从源码改系统提示词的原因。

安装

1. 创建配置文件

手动创建 %userprofile%/.config/opencode/opencode.json

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["./plugins/slim-prompt.ts"]
}

2. 放置插件

复制 slim-prompt.ts%userprofile%/.config/opencode/plugins/

3. 放置提示词

在提示词目录放入 default.txt

  • 提示词目录优先级
  1. 环境变量自定义文件夹: Windows PowerShell命令 setx OPENCODE_PROMPTS_DIR ".\prompt",(设置后重启终端生效)
  2. 或者使用默认文件夹(需手动创建): %userprofile%/.config/opencode/prompt

4. 重启生效


原理解析

hook 在发送消息前触发,通过 "\nYou are powered by the model named " 定位分割点,保留模型信息及之后内容,只替换基础提示词。

代码

slim-prompt.ts

/**
 * Opencode系统提示词精简插件
 * 
 * 基于 @audriussagadinas 和 @distbit0 的方案改进
 * 原版 Issue: https://github.com/anomalyco/opencode/issues/7101
 * 
 * 功能:
 * - 根据模型 ID 选择对应的提示词文件(与官方命名一致)
 * - 使用模型信息行作为分隔符,更精确地定位替换位置
 * - 只替换基础提示词部分,保留模型信息及之后的所有内容
 * 
 * 提示词文件目录优先级:
 * 1. 环境变量 OPENCODE_PROMPTS_DIR
 * 2. 默认: ~/.config/opencode/prompt
 * 
 * 使用方法:
 * 1. 在 .opencode/plugins/ 目录放置此文件
 * 2. 在提示词目录放置模型对应文件,默认使用default.txt,有手动指定模型的需求请自行修改promptFilename函数
 * 3. 在 opencode.json 添加 "plugin": ["./plugins/slim-prompt"]
 */

import { readFileSync } from "node:fs"
import { join } from "path"
import { homedir } from "os"

/** @typedef {import("@opencode-ai/plugin").Plugin} Plugin */

// 提示词目录: 环境变量优先,否则默认用户目录
const PROMPTS_DIR = process.env.OPENCODE_PROMPTS_DIR || join(homedir(), ".config", "opencode", "prompt")

// 根据模型 ID 选择对应的提示词文件
function promptFilename(model) {
  const modelId = String(model?.api?.id ?? "").toLowerCase()

// 因为我不用这些模型,所以默认使用 "default.txt"
//   if (modelId.includes("claude") || modelId.includes("anthropic")) return join(PROMPTS_DIR, "anthropic.txt")
//   if (modelId.includes("codex")) return join(PROMPTS_DIR, "codex.txt")
//   if (modelId.includes("gpt-4") || modelId.includes("o1") || modelId.includes("o3")) return join(PROMPTS_DIR, "beast.txt")
//   if (modelId.includes("gemini")) return join(PROMPTS_DIR, "gemini.txt")
//   if (modelId.includes("trinity")) return join(PROMPTS_DIR, "trinity.txt")
//   if (modelId.includes("kimi")) return join(PROMPTS_DIR, "kimi.txt")
  
  return join(PROMPTS_DIR, "default.txt")
}

// 检查字符串是否为基础提示词(OpenCode 默认开头)

function isBasePrompt(system) {
  return system.startsWith("You are OpenCode") || system.startsWith("You are opencode")
}


function replaceBasePrompt(system, replacement) {
  // 找到模型信息行的起始位置
  const boundary = system.indexOf("\nYou are powered by the model named ")
  if (boundary === -1) return null

  // 检查分割点前是否是基础提示词
  const prefix = system.slice(0, boundary)
  if (!isBasePrompt(prefix)) return null

  // 替换: 新提示词 + 原始内容中分割点之后的所有内容
  // 只替换基础提示词部分,保留模型信息、环境信息、skills、AGENTS.md
  return replacement + system.slice(boundary)
}

/** @type {Plugin} */
export const SlimPromptPlugin = async () => ({
  "experimental.chat.system.transform": async (input, output) => {
    const [system] = output.system
    if (!system) return

    // 根据模型选择对应的提示词文件
    const shortPrompt = readFileSync(
      promptFilename(input?.model),
       "utf8",
      ).trim()

    // 执行保守替换
    const next = replaceBasePrompt(system, shortPrompt)
    if (!next) return

    // 更新系统提示词
    output.system.splice(0, output.system.length, next)
  },
})

export default SlimPromptPlugin

补充:调试钩子

如需查看完整系统提示词结构,可安装 hook-debug.ts

{ "plugin": ["./plugins/slim-prompt.ts", "./plugins/hook-debug.ts"] }

输出保存在 ~/.config/opencode/plugins/debug/ 目录。

hook-debug.ts

/**
 * Hook 调试插件 - 用于捕获系统提示词的完整上下文
 * 
 * 用途: 捕获 experimental.chat.system.transform hook 的输入输出
 *      帮助理解 OpenCode 系统提示词的结构
 * 
 * 输出: 插件同目录下的 JSON 文件
 */

import type { Plugin } from "@opencode-ai/plugin"
import { existsSync, mkdirSync, writeFileSync } from "fs"
import { join } from "path"

export const HookDebugPlugin: Plugin = async () => ({
  "experimental.chat.system.transform": async (input, output) => {
    // 构建调试对象
    const debug: any = {
      timestamp: new Date().toISOString(),
      sessionId: input?.sessionID ?? "unknown",
      model: input?.model ?? null,
      systemLength: output.system?.[0]?.length ?? 0,
      // 完整的原始系统提示词
      systemRaw: output.system?.[0] ?? "",
    }

    try {
      // 生成文件名: hook-debug-ses_xxx-2026-04-18T12-00-00.123.json
      const filename = `hook-debug-${debug.sessionId}-${debug.timestamp.replace(/[:.]/g, "-")}.json`

      // 在写入前检查并创建 debug 文件夹
      const debugDir = join(__dirname, "debug")
      if (!existsSync(debugDir)) {
        mkdirSync(debugDir, { recursive: true })
      }
      // 输出文件放到 debug/ 子文件夹
      const filepath = join(__dirname, "debug", filename)
      
      writeFileSync(filepath, JSON.stringify(debug, null, 2))
      // console.log生成的信息可能会覆盖TUI界面
      // console.log(`[HookDebug] Saved: ${filename}`)
    } catch (err: any) {
      // 写入失败时,将错误信息也写入调试对象
      debug.error = err.message ?? String(err)
      debug.errorTime = new Date().toISOString()
      
      // 尝试写入错误日志文件
      try {
        const errorFilename = `hook-debug-error-${Date.now()}.json`
        writeFileSync(join(__dirname, "debug", errorFilename), JSON.stringify(debug, null, 2))
      } catch {}
      
      console.error("[HookDebug] Save failed:", err.message)
    }
  },
})
2 个赞

加油.等待一个把iflow cli借尸还魂到opencode上.

1 个赞