标签 OpenCode 下的文章

2026年开年又迎来AI领域“ChatGPT时刻”,GitHub爆火的开源AI助手OpenClaw(原名Clawdbot、Moltbot)上线后在全球迅速走红。用户无需打开各类网站或应用,直接通过通讯软件发送消息即可下达指令、执行任务。不少海外用户已在苹果Mac mini上实现OpenClaw本地部署。 

当OpenClaw这类“全职AI员工”加速出圈,中国也不乏能打的智能体产品。枫清科技(Fabarta)近期将推出搭载Fabarta个人专属智能体的基于苹果Mac mini 预装版,主打“开箱即用、本地安全、数据永不离开设备”的超性价比解决方案。该方案依托Apple Silicon(M4)平台的高能效算力,并联合国际头部产业伙伴推进落地交付,让本地AI从“能跑”走向“好用、可管、可规模化部署”。 

该产品基于苹果M4芯片平台,深度融合本地执行能力,可直接操作文件、浏览器及终端命令,实现文件系统管理、浏览器操作、代码生成、数据抓取等复杂任务,全面覆盖企业办公、开发运维及科研场景需求。 

枫清科技(Fabarta)此举旨在推动本地AI普及,为注重数据安全与效率的企业提供新一代生产力工具。 

Fabarta个人专属智能体基于苹果Mac mini预装版可支持个性化工作流推荐与多轮对话记忆,主动适配用户习惯,并兼容OpenCode技能生态,可调用本地工具链实现自动化,将提供更“懂你”的智能体验——更关键的是,Fabarta 的“个人记忆库”默认在本地持续沉淀:你的常用流程、偏好、术语、项目背景与常用文件路径会在设备端逐步形成可控的长期记忆,越用越贴合,但不必上云。

该产品的所有数据本地操作设置白名单功能,具备更安全的数据保障;同时,其整合Mac mini M4芯片与Fabarta智能体平台,并预置垂直行业技能包,降低技术门槛,软硬一体打造高性价比的生产力支持。

在合作层面,国际稳定可靠的终端算力与生态底座、国内全渠道与交付能力,枫清科技提供Fabarta 智能体平台与行业技能包,各方共同推动本地AI在真实业务中可复制、可交付、可规模化落地。

现在有了 ai 我遇到不懂的方法直接让 ai 分析输入输出和调用关系直接就出来了
例如:opencode 的源代码

用户发送消息
      ↓
┌─────────────────────────────────────────────────────────────┐
│  Server (routes/session.ts:733)                             │
│  SessionPrompt.prompt({ ...body, sessionID })               │
└─────────────────────┬───────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────────────────────┐
│  SessionPrompt.prompt (prompt.ts:151)                       │
│  1. 创建用户消息                                             │
│  2. 调用 loop(sessionID)                                    │
└─────────────────────┬───────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────────────────────┐
│  SessionPrompt.loop (prompt.ts:258)                         │
│  while (true) {                                             │
│    1. 获取 Agent 配置: Agent.get(lastUser.agent)            │
│    2. 解析工具: resolveTools({ agent, session, ... })       │
│    3. 创建处理器: SessionProcessor.create(...)              │
│    4. 调用处理器: processor.process({ user, agent, ... })   │
│  }                                                          │
└─────────────────────┬───────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────────────────────┐
│  SessionProcessor.process (processor.ts:45)                 │
│  while (true) {                                             │
│    1. 调用 LLM: LLM.stream(streamInput)                     │
│    2. 处理流式响应:                                          │
│       - reasoning-delta → 更新推理部分                       │
│       - text-delta → 更新文本部分                            │
│       - tool-call → 执行工具                                 │
│    3. 工具执行完成后继续循环                                  │
│  }                                                          │
└─────────────────────┬───────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────────────────────┐
│  LLM.stream (llm.ts)                                        │
│  1. 构建系统提示词                                           │
│  2. 调用 AI SDK: streamText({ model, messages, tools })     │
│  3. 返回流式响应                                             │
└─────────────────────────────────────────────────────────────┘

TUI ↔ Server 通信机制

架构图

┌─────────────────────────────────────────────────────────────┐
│  主线程 (thread.ts)                                         │
│  - 运行 TUI 界面                                            │
│  - 创建 RPC 客户端                                          │
└─────────────────────┬───────────────────────────────────────┘
                      │ RPC 通信
                      ▼
┌─────────────────────────────────────────────────────────────┐
│  Worker 线程 (worker.ts)                                    │
│  - 运行 Server.App()                                        │
│  - 处理 fetch 请求                                          │
│  - 转发事件                                                 │
└─────────────────────────────────────────────────────────────┘

Worker 启动流程

用户运行 `opencode`
         ↓
index.ts 解析命令 → TuiThreadCommand ($0 默认命令)
         ↓
thread.ts handler 执行:
         ↓
第 79-85 行:确定 worker 文件路径
         ↓
第 93 行:创建 Worker 线程
   const worker = new Worker(workerPath, { env: ... })
         ↓
第 101 行:创建 RPC 客户端与 Worker 通信
   const client = Rpc.client<typeof rpc>(worker)
         ↓
第 143 行:启动 TUI 界面
   const tuiPromise = tui({ url, fetch: customFetch, ... })

之前没有 ai 的时候经常一个方法看半天看不懂

最近一直在玩 vibe coding ,主力工具是 OpenCodeGemini CLI,用着其实挺顺手,基本需求都能 cover 。
但用久了,发现这哥俩也是各有特点:

  1. OpenCode 是我的“远程工作站”。最大的爽点就是部署灵活,很方便远程访问,而且能自由“换脑”——我现在主力搭配的是 MiniMax 2.1 / Code Plan - Plus ,响应快得飞起,额度还根本用不完……所以一些轻量级或者需要快速验证想法的项目,我都会丢给它。其他模型还没怎么试,感觉这个组合已经够香了

  2. Gemini CLI 则是我的“精准武器”。特别是处理 Rust 项目的时候,Gemini 3 Pro 的表现明显比 MiniMax 更稳、更准,那种“懂我”的感觉很到位。但痛点也很明显:额度太不经用了,虽然用用给我切到 Flash 模型,被打断的感觉很不尽兴.. 明明就快出来了...

所以,问题来了。我看 Antigravity 的使用要突破层层障碍,它有什么独特的绝活让你无法割舍的?

在线地址: https://johnsmith2078.github.io/webjrpg/

完成度比我想象地高很多,第一次直出的版本就完全能跑了,不过后续稍微迭代了一下,加了一些传统 RPG 的特性,omo 也确实是 token 消耗器,几下把我 codex 的周额度就花完了,目前流程还很短,只有十来分钟的样子,不过毕竟只是一个实验性的项目。

最近对 CRS 自己二开的项目优化和重构拉一下,并且添加拉 Opencode支持 包含Oh My OpenCode 插件支持,想看配置的直接划到下面或者直接访问 :
https://github.com/dadongwo/claude-relay-service
最近优化亮点:

  • OpenCode & Oh My OpenCode 原生支持::完美兼容 Antigravity 账户体系下的 OpenCode 配置
  • 智能风控对齐:自动注入 requestType: 'agent' 并优化 System Prompt 插入策略,降低被上游风控拦截的概率。
  • 智能重试与切换账号:针对 Antigravity 429 Resource Exhausted 深度解析(区分 Quota/RateLimit/Capacity),自动清理会话并切换账号重试。
  • 模型级智能冷却:支持对 Claude/Opus/Flash 等不同模型分别计算冷却时间,避免因单一模型限流影响整个账号使用。UI页面展示:

Note: 若某个模型触发了限流,此处还会显示该模型的 冷却倒计时 (Cooling Down),方便您了解何时可以恢复使用。

小更新 针对Antigravity 账户的gemin 模型兼容和工具调用对齐 解决 最新429 问题 mcp工具调用兼容等等

需要的可自取:
https://github.com/dadongwo/claude-relay-service/blob/main/README.md

OpenCode 集成配置

在用户目录下的 .config\opencode\opencode.json 文件中配置 provider

// antigravity 配置示例 "antigravity": { "npm": "@ai-sdk/anthropic", "name": "Antigravity", "options": { "baseURL": "http://localhost:3000/antigravity/api/v1", "apiKey": "cr_XXXXXXXXX" }, "models": { "claude-opus-4-5-thinking": { "name": "Claude Opus 4.5 Thinking", "thinking": true, "limit": { "context": 200000, "output": 8192 }, "modalities": { "input": ["text", "image"], "output": ["text"] } }, "claude-sonnet-4-5-thinking": { "name": "Claude Sonnet 4.5 Thinking", "thinking": true, "limit": { "context": 200000, "output": 8192 }, "modalities": { "input": ["text", "image"], "output": ["text"] } }, "gemini-3-flash-preview": { "name": "Gemini 3 Flash Preview", "attachment": true, "limit": { "context": 1000000, "output": 8192 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } }, "gemini-3-pro-preview": { "name": "Gemini 3 Pro Preview", "thinking": true, "attachment": true, "limit": { "context": 1000000, "output": 8192 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } } } }, // codex cli 接入账户示例 "openai-custom": { "npm": "@ai-sdk/openai", "name": "OpenAI Custom", "options": { "baseURL": "http://localhost:3200/openai", "apiKey": "cr_xxxxxxxxxxxxxxxx" }, "models": { "gpt-5.2": { "name": "GPT 5.2 (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "none": { "reasoningEffort": "none", "reasoningSummary": "auto", "textVerbosity": "medium" }, "low": { "reasoningEffort": "low", "reasoningSummary": "auto", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "auto", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } }, "gpt-5.2-codex": { "name": "GPT 5.2 Codex (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "low": { "reasoningEffort": "low", "reasoningSummary": "auto", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "auto", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } }, "gpt-5.1-codex-max": { "name": "GPT 5.1 Codex Max (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "low": { "reasoningEffort": "low", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } } } } 

oh-my-opencode.json 配置文件示例:

{ "$schema": "XXXXXXXXX", "google_auth": false, "agents": { "sisyphus": { "model": "antigravity/claude-opus-4-5-thinking" }, "oracle": { "model": "openai-custom/gpt-5.2" }, "librarian": { "model": "opencode/glm-4.7-free" }, "explore": { "model": "antigravity/gemini-3-flash-preview" }, "frontend-ui-ux-engineer": { "model": "antigravity/gemini-3-pro-preview" }, "document-writer": { "model": "antigravity/gemini-3-flash-preview" }, "multimodal-looker": { "model": "antigravity/gemini-3-flash-preview" } } } 

欢迎讨论和提问,谢谢各位佬。


📌 转载信息
转载时间: 2026/1/25 23:18:08

最近对 CRS 自己二开的项目优化和重构拉一下,并且添加拉 Opencode支持 包含Oh My OpenCode 插件支持,想看配置的直接划到下面或者直接访问 :
https://github.com/dadongwo/claude-relay-service
最近优化亮点:

  • OpenCode & Oh My OpenCode 原生支持::完美兼容 Antigravity 账户体系下的 OpenCode 配置
  • 智能风控对齐:自动注入 requestType: 'agent' 并优化 System Prompt 插入策略,降低被上游风控拦截的概率。
  • 智能重试与切换账号:针对 Antigravity 429 Resource Exhausted 深度解析(区分 Quota/RateLimit/Capacity),自动清理会话并切换账号重试。
  • 模型级智能冷却:支持对 Claude/Opus/Flash 等不同模型分别计算冷却时间,避免因单一模型限流影响整个账号使用。UI页面展示:

Note: 若某个模型触发了限流,此处还会显示该模型的 冷却倒计时 (Cooling Down),方便您了解何时可以恢复使用。

小更新 针对Antigravity 账户的gemin 模型兼容和工具调用对齐 解决 最新429 问题 mcp工具调用兼容等等

需要的可自取:
https://github.com/dadongwo/claude-relay-service/blob/main/README.md

OpenCode 集成配置

在用户目录下的 .config\opencode\opencode.json 文件中配置 provider

// antigravity 配置示例 "antigravity": { "npm": "@ai-sdk/anthropic", "name": "Antigravity", "options": { "baseURL": "http://localhost:3000/antigravity/api/v1", "apiKey": "cr_XXXXXXXXX" }, "models": { "claude-opus-4-5-thinking": { "name": "Claude Opus 4.5 Thinking", "thinking": true, "limit": { "context": 200000, "output": 8192 }, "modalities": { "input": ["text", "image"], "output": ["text"] } }, "claude-sonnet-4-5-thinking": { "name": "Claude Sonnet 4.5 Thinking", "thinking": true, "limit": { "context": 200000, "output": 8192 }, "modalities": { "input": ["text", "image"], "output": ["text"] } }, "gemini-3-flash-preview": { "name": "Gemini 3 Flash Preview", "attachment": true, "limit": { "context": 1000000, "output": 8192 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } }, "gemini-3-pro-preview": { "name": "Gemini 3 Pro Preview", "thinking": true, "attachment": true, "limit": { "context": 1000000, "output": 8192 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } } } }, // codex cli 接入账户示例 "openai-custom": { "npm": "@ai-sdk/openai", "name": "OpenAI Custom", "options": { "baseURL": "http://localhost:3200/openai", "apiKey": "cr_xxxxxxxxxxxxxxxx" }, "models": { "gpt-5.2": { "name": "GPT 5.2 (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "none": { "reasoningEffort": "none", "reasoningSummary": "auto", "textVerbosity": "medium" }, "low": { "reasoningEffort": "low", "reasoningSummary": "auto", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "auto", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } }, "gpt-5.2-codex": { "name": "GPT 5.2 Codex (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "low": { "reasoningEffort": "low", "reasoningSummary": "auto", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "auto", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } }, "gpt-5.1-codex-max": { "name": "GPT 5.1 Codex Max (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "low": { "reasoningEffort": "low", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } } } } 

oh-my-opencode.json 配置文件示例:

{ "$schema": "XXXXXXXXX", "google_auth": false, "agents": { "sisyphus": { "model": "antigravity/claude-opus-4-5-thinking" }, "oracle": { "model": "openai-custom/gpt-5.2" }, "librarian": { "model": "opencode/glm-4.7-free" }, "explore": { "model": "antigravity/gemini-3-flash-preview" }, "frontend-ui-ux-engineer": { "model": "antigravity/gemini-3-pro-preview" }, "document-writer": { "model": "antigravity/gemini-3-flash-preview" }, "multimodal-looker": { "model": "antigravity/gemini-3-flash-preview" } } } 

欢迎讨论和提问,谢谢各位佬。


📌 转载信息
转载时间: 2026/1/25 23:15:37

我和同事们目前正在利用 OpenCode(Claude Code 的一个替代方案)进行一项小研究。目前我们刚开始测试 gpt-5-mini,看看它在编程辅助方面的性能能否与 Claude Sonnet 媲美。

也非常欢迎大家亲自尝试研究并分享心得,比如哪种 LLM 模型成功率最高,以及使用了什么样的 Prompt(提示词)。

注:我是来自印尼的用户,正在使用 Google Gemini 协助翻译论坛里的讨论。如果翻译有不准确的地方,还请大家多多包涵,手下留情。


📌 转载信息
原作者: galpt
转载时间: 2026/1/25 08:06:35

受到影响的版本为 1.1.33 和 1.1.34
如果为桌面版,1.1.34 解决了一些问题,但 web 端并未解决

今天一觉醒来,mac 端和 Linux 端的 Opencode 都报错了
我是通过 Opencode web 的方式,在浏览器里使用的
这样我可以在我的 Windows 上同时操控多个 opencode 的多个 session

(ps: 在这个页面里尽管更新到了 1.1.34 依旧会显示版本为 1.1.33)

OpenCode Desktop v1.1.33 版本界面显示空白问题 Github Issue #10136
很显然,我在 issue 里找到了很多受到该 bug 影响的人们,证明我并不是一个人

如果使用 web 端来使用 Opencode 的话,似乎只能回退到 1.1.32 或更早来解决
目前的话最新的 1.1.34 版本并未解决该问题,或许要等待下一个版本

额外的,TUI 是正常的,所以如果在使用 TUI 的话不必担心

另外,感觉 web 端使用起来比 TUI 要好呢,安利一下:p


📌 转载信息
原作者:
ufhy
转载时间:
2026/1/23 19:24:35

花了两三天时间,查了大量资料,终于把 oh-my-opencode 配置明白了。

注意: 以下所有配置均已进行脱敏处理(隐藏了具体的 session_id、本机用户路径、工作区路径以及真实的 provider 地址)。
无广 我自用的 provider 未完全脱敏,感觉脱敏看起来好奇怪,model key 我懒得处理。 个人不推荐任何 provider


Agents 与 Categories 自述

AI 生成出来的,各类代理(Agents)和分类(Categories)的职责:

{ "agents": { "build": { "agent": "build", "model": "未知", "suitable_for": [ "代码实现与重构", "Bug 定位与修复", "单元测试与测试策略", "构建与CI排障", "项目结构与可维护性优化" ], "core_principles": [ "先澄清需求与边界", "最小改动达成目标", "保持一致性与可读性", "优先可靠性与可测试性", "避免过度设计(YAGNI)", "减少重复(DRY)", "遵循SOLID与KISS" ] }, "plan": { "agent": "plan", "model": "未知", "suitable_for": [ "需求澄清与范围界定", "技术方案设计与权衡", "任务拆解与里程碑规划", "风险识别与应对预案", "验收标准与测试策略制定" ], "core_principles": [ "先理解问题再提出方案", "以最小可行改动达成目标", "优先降低风险与不确定性", "明确假设、边界与验收标准", "可执行、可验证、可回滚", "沟通简洁且信息密度高" ] }, "general": { "agent": "general", "model": "未知", "suitable_for": [ "通用问答与信息整理", "编程概念解释与示例", "需求澄清与方案对比", "写作润色与结构化输出", "基础调试思路与排错建议" ], "core_principles": [ "遵循指令优先级:系统 > 开发者 > 用户", "不编造事实;不确定时明确说明", "输出清晰、简洁、可执行", "在安全与隐私上保持保守", "按受众调整表达与细节深度", "优先给出可验证的步骤与依据" ] }, "explore": { "agent": "explore", "model": "未知", "suitable_for": [ "探索代码库模式", "分析文件结构", "使用AST-grep搜索", "并行执行搜索任务", "识别代码模式", "构建代码索引" ], "core_principles": [ "最大化搜索努力", "并行启动多个代理", "不限于第一个结果", "彻底且详尽", "使用多种工具", "忽略无关上下文", "专注于内置角色" ] }, "librarian": { "agent": "librarian", "model": "Antigravity", "suitable_for": [ "开源代码库深度解析", "第三方库 API 使用咨询", "代码实现逻辑溯源", "库的版本差异与历史变更分析", "获取 GitHub 源码实证与 Permalinks" ], "core_principles": [ "证据优先:所有技术主张必须附带 GitHub 永久链接证据", "时效感知:严格区分过时信息,优先检索 2025+ 最新数据", "深度溯源:通过克隆仓库、分析提交历史和代码逻辑提供答案", "多维调研:结合官方文档、源码搜索与 Issue/PR 背景", "引用规范:严格执行强制性代码片段与路径引用格式", "分类执行:针对概念、实现、上下文采用定制化搜索策略" ] }, "oracle": { "agent": "oracle", "model": "未知", "suitable_for": [ "系统架构与技术选型评审", "复杂问题根因分析与排障策略", "重构路线规划与风险控制", "代码库结构解读与一致性治理", "性能与可靠性权衡建议" ], "core_principles": [ "KISS:优先最简单可行方案", "YAGNI:只做当前明确需求", "复用优先:尽量沿用现有模式与依赖", "可维护性优先:降低认知负担", "单一路径:给出一个主推荐方案", "投入可见:明确工作量与边界条件", "止于足够好:避免过度优化与过度设计" ] }, "multimodal-looker": { "agent": "multimodal-looker", "model": "未知", "suitable_for": [ "解析图片、PDF 等非纯文本媒体文件", "提取文档中的特定章节、表格或结构化数据", "描述 UI 界面布局、交互元素及视觉样式", "分析并解释流程图、架构图等复杂图表逻辑", "从多模态内容中获取深度理解而非原始文字" ], "core_principles": [ "仅提取用户请求的相关信息,严禁输出无关干扰项", "深度分析多媒体文件的视觉结构与内在联系", "通过返回解析后的核心结论来节省上下文 Token", "在目标任务上保持彻底详尽,对非核心内容保持简洁", "响应语言必须与用户请求的语言严格保持一致", "不处理可由常规读取工具处理的纯文本或源代码" ] } }, "categories": { "visual-engineering": { "category": "视觉工程", "model": "google/antigravity-gemini-3-pro", "variant": "无", "suitable_for": [ "将设计稿转为可维护的前端实现(HTML/CSS/JS/组件化)", "构建高一致性的设计系统与组件库(令牌、主题、栅格、排版)", "复杂布局与动效工程化(响应式、过渡、时间线、性能约束)", "可视化与图形界面开发(图表、画布、WebGL/着色器协同)", "UI 性能优化(首屏、渲染抖动、资源加载、动画合成层)", "可访问性与跨端适配(A11y、触控、不同 DPR/字体渲染差异)" ], "core_principles": [ "以视觉规格为真:像素密度、间距、排版层级、色彩与对比度可量化可验证", "工程可演进:组件边界清晰、样式可组合、避免脆弱选择器与一次性 hack", "一致性优先:设计令牌驱动(颜色/间距/圆角/阴影/字体),减少特例", "性能与质感并重:动画遵循合成友好、控制重排重绘、资源预算清晰", "跨设备稳健:移动优先、断点策略明确、避免依赖单一浏览器特性", "可访问性默认开启:语义结构、焦点管理、键盘可达、对比度与动效可降级" ] }, "ultrabrain": { "category": "预设类别:ultrabrain", "model": "openai/gpt-5.2-codex", "variant": "high", "suitable_for": [ "高难度软件工程设计与架构推演", "复杂代码库的系统性重构与性能优化", "并发、分布式、可靠性与故障演练方案设计", "严格约束下的推理、验证与形式化思维辅助", "跨语言/跨栈问题定位、根因分析与修复策略制定" ], "core_principles": [ "以正确性与可验证性优先:先定义不变量、边界与失败模式,再给方案", "深度推理但输出克制:内部充分思考,外部给最少且必要的结论与步骤", "面向工程落地:权衡成本、风险、可维护性与可观测性,避免纸上架构", "高标准代码质量:强调可读性、模块化、清晰接口与测试友好", "系统化排障:基于证据(日志/指标/复现)进行根因定位,避免猜测", "遵循约束与安全:最小权限、避免破坏性操作、对不可逆变更显式提示" ] }, "artistry": { "category": "艺术表现", "model": "google/antigravity-gemini-3-pro", "variant": "高", "suitable_for": [ "文案与叙事创作", "诗歌与意象化表达", "品牌语气与风格统一", "视觉概念与艺术指导", "舞台/影视/游戏世界观设定", "创意改写与风格迁移" ], "core_principles": [ "以审美一致性为先:语气、节奏、意象统一", "高原创与高密度表达:避免陈词滥调与模板化", "强调画面感与隐喻:用具体意象承载抽象主题", "结构服务情绪推进:起承转合清晰但不僵硬", "细节可信:材质、光影、气味、触感等感官要素可落地", "尊重约束:在题材、风格、篇幅、受众限制内创造", "文化与语境敏感:避免不当挪用与刻板印象" ] }, "quick": { "category": "快速", "model": "opencode/grok-code", "variant": "无", "suitable_for": [ "快速排查与定位问题", "短小明确的代码修改与补丁", "命令行与脚本使用指导", "代码片段解释与改写", "高频迭代的工程协作问答" ], "core_principles": [ "速度优先,尽快给出可执行方案", "聚焦当前任务,避免过度设计", "最小可行修改,降低引入风险", "清晰直接的步骤与结论", "必要时补充关键边界与注意事项" ] }, "unspecified-low": { "category": "未指定-低", "model": "xaio/Qwen3-Coder-30B-A3B-Instruct", "variant": "无", "suitable_for": [ "日常编程问答与示例代码生成", "小到中等规模的功能实现与重构建议", "快速调试思路梳理与错误定位指导", "常见框架与工具链的使用说明", "代码评审要点与改进清单输出" ], "core_principles": [ "以可运行与可维护为优先,避免过度设计", "输出简洁直接,默认给出可落地的实现路径", "不确定处明确标注假设与边界条件", "遵循通用工程最佳实践(清晰接口、低耦合、可测试)", "优先最小改动修复与渐进式优化", "在安全与可靠性相关问题上保持保守与可解释" ] }, "unspecified-high": { "category": "未指定-高配", "model": "xaio/Qwen3-Coder-480B-A35B-Instruct", "variant": "无", "suitable_for": [ "复杂代码生成与重构", "大型代码库理解与跨文件推理", "架构设计与技术方案评审", "高难度调试与根因分析", "多语言全栈开发与集成", "高质量测试用例与边界条件覆盖" ], "core_principles": [ "优先正确性与可验证性:输出可编译、可运行、可测试的方案", "工程化与可维护性:结构清晰、接口稳定、最小必要复杂度", "安全与稳健:默认防御性设计,避免注入、越权与数据破坏", "明确假设与约束:对不确定点给出可选项与取舍依据", "高信噪比:少废话、直达结论,提供可执行步骤与关键细节", "一致性与可读性:遵循既有风格与约定,减少认知负担" ] }, "writing": { "category": "写作", "model": "google/antigravity-gemini-3-flash", "variant": "无", "suitable_for": [ "文章与博客撰写", "营销文案与品牌语气改写", "邮件与公文写作", "故事与创意写作", "多版本改写与润色压缩", "结构化大纲与标题生成" ], "core_principles": [ "清晰表达,避免含混与赘述", "以读者为中心,匹配语气与场景", "结构先行:先框架后细节", "信息准确,不编造不可核验事实", "风格一致,术语与用词统一", "可编辑性强:给出可直接替换的文本产出" ] } } } 


插件主配置文件:oh-my-opencode.json

定义具体的模型映射。
配置文件路径参考:

  • Linux/macOS: ~/.config/opencode/oh-my-opencode.json
  • Windows: %USERPROFILE%\.config\opencode\oh-my-opencode.json
{ "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json", "google_auth": false, "agents": { "Sisyphus": { "model": "google/antigravity-claude-opus-4-5-thinking", "memo": "主协调者 - 默认主智能体,负责整体任务的规划、委派和执行协调" }, "Sisyphus-Junior": { "model": "google/antigravity-claude-sonnet-4-5", "memo": "专注执行者 - 执行单元,直接编写代码,不能再委派任务,模型由category动态决定(此为兜底)" }, "Prometheus (Planner)": { "model": "openai/gpt-5.2", "memo": "规划师 - 任务规划,使用工作规划方法论进行任务分解和策略制定" }, "Metis (Plan Consultant)": { "model": "google/antigravity-claude-sonnet-4-5", "memo": "计划顾问 - 预规划分析,识别隐藏需求和潜在的AI失败点" }, "Momus (Plan Reviewer)": { "model": "google/antigravity-claude-sonnet-4-5-thinking", "memo": "计划审查员 - 计划审查,对生成的计划进行质量检查和风险评估" }, "oracle": { "model": "openai/gpt-5.2", "memo": "架构师 - 架构设计、代码审查、战略规划,利用GPT-5.2的逻辑推理能力" }, "librarian": { "model": "google/antigravity-gemini-3-flash", "memo": "资料管理员 - 多仓库分析、文档查找、实现示例搜索,深度代码库理解和GitHub研究" }, "explore": { "model": "opencode/grok-code", "memo": "探索者 - 快速代码库探索和模式匹配,专注于代码搜索和发现" }, "multimodal-looker": { "model": "google/antigravity-gemini-3-flash", "memo": "多模态观察者 - 视觉内容专家,分析PDF、图像、图表等多媒体内容" }, "OpenCode-Builder": { "model": "xaio-openai/Qwen3-Coder-480B-A35B-Instruct", "memo": "构建专家 - OpenCode原生build agent,默认禁用(被Sisyphus-Junior替代),需手动启用" }, "frontend-ui-ux-engineer": { "model": "google/antigravity-gemini-3-pro", "memo": "前端UI/UX工程师 - 前端开发,创建美观的用户界面,专注于创意和视觉设计" }, "document-writer": { "model": "google/antigravity-gemini-3-flash", "memo": "文档写手 - 技术写作专家,擅长流畅的技术文档写作" } }, "categories": { "visual-engineering": { "model": "google/antigravity-gemini-3-pro", "memo": "前端工程师 - 前端开发、UI/UX设计、样式调整、动画效果,专注于视觉呈现" }, "ultrabrain": { "model": "openai/gpt-5.2-codex", "variant": "high", "memo": "超级大脑 - 深度逻辑推理、复杂架构决策、需要大量分析的高难度问题" }, "artistry": { "model": "google/antigravity-gemini-3-pro", "variant": "high", "memo": "艺术家 - 高度创意任务、艺术性工作、新颖独特的想法生成" }, "quick": { "model": "opencode/grok-code", "memo": "快速执行者 - 简单任务、单文件修改、拼写修复、小改动,省钱省时" }, "unspecified-low": { "model": "xaio-openai/Qwen3-Coder-30B-A3B-Instruct", "memo": "通用助手(轻量) - 不适合其他类别的中等难度任务" }, "unspecified-high": { "model": "xaio-openai/Qwen3-Coder-480B-A35B-Instruct", "memo": "通用助手(重量) - 不适合其他类别的高难度复杂任务" }, "writing": { "model": "google/antigravity-gemini-3-flash", "memo": "文档写手 - 通用文案、技术文档编写、README撰写、注释完善、技术写作" } } } 


opencode 配置文件:opencode.json

配置文件路径参考:

  • Linux/macOS: ~/.config/opencode/opencode.json
  • Windows: %USERPROFILE%\.config\opencode\opencode.json
{ "$schema": "https://opencode.ai/config.json", "plugin": [ "oh-my-opencode", "opencode-antigravity-auth@1.3.0" ] } 

opencode-antigravity-auth 的配置

{ "$schema": "https://opencode.ai/config.json", "plugin": ["opencode-antigravity-auth@latest"], "provider": { "google": { "models": { "antigravity-gemini-3-pro": { "name": "Gemini 3 Pro (Antigravity)", "limit": { "context": 1048576, "output": 65535 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }, "variants": { "low": { "thinkingLevel": "low" }, "high": { "thinkingLevel": "high" } } }, "antigravity-gemini-3-flash": { "name": "Gemini 3 Flash (Antigravity)", "limit": { "context": 1048576, "output": 65536 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }, "variants": { "minimal": { "thinkingLevel": "minimal" }, "low": { "thinkingLevel": "low" }, "medium": { "thinkingLevel": "medium" }, "high": { "thinkingLevel": "high" } } }, "antigravity-claude-sonnet-4-5": { "name": "Claude Sonnet 4.5 (Antigravity)", "limit": { "context": 200000, "output": 64000 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } }, "antigravity-claude-sonnet-4-5-thinking": { "name": "Claude Sonnet 4.5 Thinking (Antigravity)", "limit": { "context": 200000, "output": 64000 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }, "variants": { "low": { "thinkingConfig": { "thinkingBudget": 8192 } }, "max": { "thinkingConfig": { "thinkingBudget": 32768 } } } }, "antigravity-claude-opus-4-5-thinking": { "name": "Claude Opus 4.5 Thinking (Antigravity)", "limit": { "context": 200000, "output": 64000 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }, "variants": { "low": { "thinkingConfig": { "thinkingBudget": 8192 } }, "max": { "thinkingConfig": { "thinkingBudget": 32768 } } } }, "gemini-2.5-flash": { "name": "Gemini 2.5 Flash (Gemini CLI)", "limit": { "context": 1048576, "output": 65536 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } }, "gemini-2.5-pro": { "name": "Gemini 2.5 Pro (Gemini CLI)", "limit": { "context": 1048576, "output": 65536 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } }, "gemini-3-flash-preview": { "name": "Gemini 3 Flash Preview (Gemini CLI)", "limit": { "context": 1048576, "output": 65536 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } }, "gemini-3-pro-preview": { "name": "Gemini 3 Pro Preview (Gemini CLI)", "limit": { "context": 1048576, "output": 65535 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } } } } } } 

我自用 provider,仅供参考。 claude 原本的 思考等级的 max 我为了方便记忆 改成了 xhigh

{ "google": { "models": { "antigravity-gemini-3-pro": { "name": "Gemini 3 Pro (Antigravity)", "limit": { "context": 1048576, "output": 65535 }, "modalities": { "input": [ "text", "image", "pdf" ], "output": [ "text" ] }, "variants": { "low": { "thinkingLevel": "low" }, "high": { "thinkingLevel": "high" } } }, "antigravity-gemini-3-flash": { "name": "Gemini 3 Flash (Antigravity)", "limit": { "context": 1048576, "output": 65536 }, "modalities": { "input": [ "text", "image", "pdf" ], "output": [ "text" ] }, "variants": { "minimal": { "thinkingLevel": "minimal" }, "low": { "thinkingLevel": "low" }, "medium": { "thinkingLevel": "medium" }, "high": { "thinkingLevel": "high" } } }, "antigravity-claude-sonnet-4-5": { "name": "Claude Sonnet 4.5 (Antigravity)", "limit": { "context": 200000, "output": 64000 }, "modalities": { "input": [ "text", "image", "pdf" ], "output": [ "text" ] } }, "antigravity-claude-sonnet-4-5-thinking": { "name": "Claude Sonnet 4.5 Thinking (Antigravity)", "limit": { "context": 200000, "output": 64000 }, "modalities": { "input": [ "text", "image", "pdf" ], "output": [ "text" ] }, "variants": { "none": { "thinkingConfig": { "thinkingBudget": 0 } }, "minimal": { "thinkingConfig": { "thinkingBudget": 4096 } }, "low": { "thinkingConfig": { "thinkingBudget": 8192 } }, "medium": { "thinkingConfig": { "thinkingBudget": 12288 } }, "high": { "thinkingConfig": { "thinkingBudget": 16384 } }, "xhigh": { "thinkingConfig": { "thinkingBudget": 32768 } } } }, "antigravity-claude-opus-4-5-thinking": { "name": "Claude Opus 4.5 Thinking (Antigravity)", "limit": { "context": 200000, "output": 64000 }, "modalities": { "input": [ "text", "image", "pdf" ], "output": [ "text" ] }, "variants": { "none": { "thinkingConfig": { "thinkingBudget": 0 } }, "minimal": { "thinkingConfig": { "thinkingBudget": 4096 } }, "low": { "thinkingConfig": { "thinkingBudget": 8192 } }, "medium": { "thinkingConfig": { "thinkingBudget": 12288 } }, "high": { "thinkingConfig": { "thinkingBudget": 16384 } }, "xhigh": { "thinkingConfig": { "thinkingBudget": 32768 } } } } } }, "openai-compatible": { "npm": "@ai-sdk/openai-compatible", "name": " X (Chat)", "options": { "baseURL": "https://x/v1" }, "models": { "Qwen3-Coder-480B-A35B-Instruct": { "name": "Qwen 3 Coder 480B", "limit": { "context": 256000, "output": 32000 } }, "Qwen3-Coder-30B-A3B-Instruct": { "name": "Qwen 3 Coder 30B", "limit": { "context": 64000, "output": 32000 } }, "XAIO-G-3-Pro-Preview": { "name": "Gemini 3 Pro Preview", "limit": { "context": 200000, "output": 32000 }, "modalities": { "input": [ "text", "image", "video" ], "output": [ "text" ] }, "reasoning": true, "interleaved": { "field": "reasoning_content" } }, "XAIO-G-3-Flash-Preview": { "name": "Gemini 3 Flash", "limit": { "context": 200000, "output": 32000 }, "modalities": { "input": [ "text", "image", "video" ], "output": [ "text" ] }, "reasoning": true, "interleaved": { "field": "reasoning_content" } } } }, "anthropic": { "npm": "@ai-sdk/anthropic", "name": " X (Anthropic)", "options": { "baseURL": "https://x/anthropic" }, "models": { "XAIO-C-4-5-Opus": { "name": "Claude Opus 4.5", "limit": { "context": 200000, "output": 32000 }, "modalities": { "input": [ "text", "image", "video" ], "output": [ "text" ] }, "variants": { "none": { "thinking": { "type": "disabled" } }, "minimal": { "thinking": { "type": "enabled", "budgetTokens": 4096 } }, "low": { "thinking": { "type": "enabled", "budgetTokens": 8192 } }, "medium": { "thinking": { "type": "enabled", "budgetTokens": 12288 } }, "high": { "thinking": { "type": "enabled", "budgetTokens": 16384 } }, "xhigh": { "thinking": { "type": "enabled", "budgetTokens": 32768 } } } }, "XAIO-C-4-5-Sonnet": { "name": "Claude Sonnet 4.5", "limit": { "context": 200000, "output": 32000 }, "modalities": { "input": [ "text", "image", "video" ], "output": [ "text" ] }, "variants": { "none": { "thinking": { "type": "disabled" } }, "minimal": { "thinking": { "type": "enabled", "budgetTokens": 4096 } }, "low": { "thinking": { "type": "enabled", "budgetTokens": 8192 } }, "medium": { "thinking": { "type": "enabled", "budgetTokens": 12288 } }, "high": { "thinking": { "type": "enabled", "budgetTokens": 16384 } }, "xhigh": { "thinking": { "type": "enabled", "budgetTokens": 32768 } } } }, "XAIO-C-4-5-Haiku": { "name": "Claude Haiku 4.5", "limit": { "context": 200000, "output": 32000 }, "modalities": { "input": [ "text", "image", "video" ], "output": [ "text" ] }, "variants": { "none": { "thinking": { "type": "disabled" } }, "minimal": { "thinking": { "type": "enabled", "budgetTokens": 4096 } }, "low": { "thinking": { "type": "enabled", "budgetTokens": 8192 } }, "medium": { "thinking": { "type": "enabled", "budgetTokens": 12288 } }, "high": { "thinking": { "type": "enabled", "budgetTokens": 16384 } }, "xhigh": { "thinking": { "type": "enabled", "budgetTokens": 32768 } } } } } }, "openai": { "npm": "@ai-sdk/openai", "name": " X (OpenAI Responses)", "options": { "baseURL": "https://x/v1" }, "models": { "XAIO-O-G5-2": { "name": "GPT-5.2", "limit": { "context": 200000, "output": 32000 }, "modalities": { "input": [ "text", "image", "video" ], "output": [ "text" ] }, "variants": { "none": { "reasoningEffort": "none" }, "minimal": { "reasoningEffort": "minimal" }, "low": { "reasoningEffort": "low" }, "medium": { "reasoningEffort": "medium" }, "high": { "reasoningEffort": "high" }, "xhigh": { "reasoningEffort": "xhigh" } } }, "XAIO-O-G5-2-Codex": { "name": "GPT-5.2 Codex", "limit": { "context": 200000, "output": 32000 }, "modalities": { "input": [ "text", "image", "video" ], "output": [ "text" ] }, "variants": { "none": { "reasoningEffort": "none" }, "minimal": { "reasoningEffort": "minimal" }, "low": { "reasoningEffort": "low" }, "medium": { "reasoningEffort": "medium" }, "high": { "reasoningEffort": "high" }, "xhigh": { "reasoningEffort": "xhigh" } } }, "XAIO-O-G5-1-Codex-Mini": { "name": "GPT-5.1 Codex mini", "limit": { "context": 200000, "output": 32000 }, "modalities": { "input": [ "text", "image", "video" ], "output": [ "text" ] }, "variants": { "none": { "reasoningEffort": "none" }, "minimal": { "reasoningEffort": "minimal" }, "low": { "reasoningEffort": "low" }, "medium": { "reasoningEffort": "medium" }, "high": { "reasoningEffort": "high" }, "xhigh": { "reasoningEffort": "xhigh" } } }, "XAIO-O-G5-1-Codex-Max": { "name": "GPT-5.1 Codex Max", "limit": { "context": 200000, "output": 32000 }, "modalities": { "input": [ "text", "image", "video" ], "output": [ "text" ] }, "variants": { "none": { "reasoningEffort": "none" }, "minimal": { "reasoningEffort": "minimal" }, "low": { "reasoningEffort": "low" }, "medium": { "reasoningEffort": "medium" }, "high": { "reasoningEffort": "high" }, "xhigh": { "reasoningEffort": "xhigh" } } } } } } 


鉴权配置 auth.json

api key 的配置和 oauth 配置在
~/.local/share/opencode /auth.json
%USERPROFILE%\.local\share\opencode\auth.json

{ "x": { "type": "api", "key": "<API_KEY>" } } 



📌 转载信息
原作者:
favorPatato
转载时间:
2026/1/23 15:36:07

前几天看到: 让 GPT5.2 在 OpenCode 里说人话思路,我进行了尝试,他使用了 gemini-3-flash 对 GPT5.2 的思考块进行翻译,我用过之后,经常翻译超时,于是想到,能不能让 GPT5.2 自己翻译自己,基于这位佬友的代码进行了二开,效果嘎嘎好。效果如图:


安装:
1、复制本帖最下方代码,保存为 think-translator.ts(文件名随意)
2、打开 think-translator.ts
3、把 GPT5.2 的中转 API 信息写到 6~8 行(加了注释了,一眼就能看到)
4、把 think-translator.ts 放到 C:\Users\xxxx (你的名字).config\opencode\plugin 里,opencode 会自动加载
5、完事,开蹬

import type { Plugin } from "@opencode-ai/plugin";
import type { Message, Part } from "@opencode-ai/sdk";

declare function require(id: string): unknown;

// OpenAI 兼容接口(可用 /v1、/v1/chat/completions 或 /v1/responses 作为 baseURL)
const TRANSLATION_BASE_URL = "https://www.right.codes/codex/v1";// 填你的订阅 地址(示例是right code)
const TRANSLATION_API_KEY = ""; // 填你的订阅 Key
const TRANSLATION_MODEL_ID = "gpt-5.2"; // 可选:gpt-5.2 | gpt-5.2-low | gpt-5.2-medium

type PartEvent = Part & {
  time?: {
    end?: unknown;
  };
  text?: string;
};

type PatchResult = {
  response?: {
    status?: number;
  };
};

type ProviderConfig = {
  baseURL: string;
  apiKey: string;
  modelID: string;
};


const START_TAG = "[〔翻译开始〕]";
const END_TAG = "[〔翻译结束〕]";

const TITLE_TRANSLATION_PREFIX = "〔译: ";
const TITLE_TRANSLATION_SUFFIX = "〕";

function stripTranslationBlocks(text: string): string {
  while (true) {
    const start = text.indexOf(START_TAG);
    if (start === -1) return text;

    const end = text.indexOf(END_TAG, start + START_TAG.length);
    if (end === -1) return text.slice(0, start).trimEnd();

    const after = end + END_TAG.length;
    let restStart = after;
    while (restStart < text.length && text[restStart] === "\n") restStart += 1;

    const left = text.slice(0, start).trimEnd();
    const right = text.slice(restStart);
    text = left ? left + "\n\n" + right : right;
  }
}

function stripTitleTranslation(title: string): string {
  const start = title.indexOf(TITLE_TRANSLATION_PREFIX);
  if (start === -1) return title.trim();

  const end = title.indexOf(TITLE_TRANSLATION_SUFFIX, start + TITLE_TRANSLATION_PREFIX.length);
  if (end === -1) return title.slice(0, start).trim();

  return (title.slice(0, start) + title.slice(end + TITLE_TRANSLATION_SUFFIX.length)).trim();
}

function applyTitleTranslation(base: string, translation: string): string {
  if (!base.startsWith("**")) return base;
  const end = base.indexOf("**", 2);
  if (end === -1) return base;

  const rawTitle = base.slice(2, end);
  const title = stripTitleTranslation(rawTitle);
  if (!title) return base;

  const suffix = translation ? ` ${TITLE_TRANSLATION_PREFIX}${translation}${TITLE_TRANSLATION_SUFFIX}` : "";
  const combined = `**${title}${suffix}**`;
  return combined + base.slice(end + 2);
}

function extractTitleAndBody(base: string): { title: string; body: string } {
  if (!base.startsWith("**")) return { title: "", body: base };

  const end = base.indexOf("**", 2);
  if (end === -1) return { title: "", body: base };

  const rawTitle = base.slice(2, end);
  const title = stripTitleTranslation(rawTitle);
  const body = base.slice(end + 2).replace(/^\s+/, "");
  return { title, body };
}

function buildTranslationBlockPending(): string {
  return `${START_TAG}\n译文生成中…\n${END_TAG}`;
}

function buildTranslationBlockDone(bodyCn: string): string {
  const text = bodyCn.trim();
  return `${START_TAG}\n${text}\n${END_TAG}`;
}


function loadProviderConfigFromOpencodeConfig(_directory: string): ProviderConfig {
  return {
    baseURL: TRANSLATION_BASE_URL,
    apiKey: TRANSLATION_API_KEY,
    modelID: TRANSLATION_MODEL_ID,
  };
}

function extractSseTextParts(data: string): string {
  let parsed: unknown;
  try {
    parsed = JSON.parse(data);
  } catch {
    return "";
  }

  // OpenAI Chat Completions streaming: { choices: [{ delta: { content: "..." } }] }
  const chat = parsed as {
    choices?: Array<{
      delta?: { content?: string; text?: string };
      message?: { content?: string };
      text?: string;
    }>;
  };

  let out = "";
  for (const choice of chat.choices ?? []) {
    const d = choice.delta;
    if (d && typeof d.content === "string") out += d.content;
    else if (d && typeof d.text === "string") out += d.text;
    else if (choice.message && typeof choice.message.content === "string") out += choice.message.content;
    else if (typeof choice.text === "string") out += choice.text;
  }
  if (out) return out;

  // OpenAI Responses streaming (common proxy format): { type: "response.output_text.delta", delta: "..." }
  const resp = parsed as {
    type?: string;
    delta?: string;
    output_text?: string;
    text?: string;
    output?: Array<{
      content?: Array<{ type?: string; text?: string }>;
    }>;
    response?: {
      output?: Array<{
        content?: Array<{ type?: string; text?: string }>; // input_text / output_text
      }>;
    };
  };

  if (resp.type && typeof resp.delta === "string") return resp.delta;
  if (typeof resp.output_text === "string") return resp.output_text;
  if (resp.type && typeof resp.text === "string" && resp.type.endsWith(".delta")) return resp.text;

  // Non-stream JSON fallbacks (responses/chat) can also land here in some proxies.
  if (resp.response?.output) {
    let joined = "";
    for (const item of resp.response.output) {
      for (const c of item.content ?? []) {
        if (typeof c.text === "string") joined += c.text;
      }
    }
    if (joined) return joined;
  }

  if (resp.output) {
    let joined = "";
    for (const item of resp.output) {
      for (const c of item.content ?? []) {
        if (typeof c.text === "string") joined += c.text;
      }
    }
    if (joined) return joined;
  }

  return "";
}

type OpenAIEndpointKind = "chat.completions" | "responses";

function resolveOpenAIEndpoint(baseURL: string): { url: string; kind: OpenAIEndpointKind } {
  const base = baseURL.replace(/\/+$/, "");
  if (base.endsWith("/chat/completions")) return { url: base, kind: "chat.completions" };
  if (base.endsWith("/responses")) return { url: base, kind: "responses" };
  return { url: `${base}/chat/completions`, kind: "chat.completions" };
}

function buildTranslationPrompt(english: string): string {
  return (
    "You are a translation engine. Translate the English content to Simplified Chinese.\n" +
    "Rules (STRICT):\n" +
    "- Output ONLY the Chinese translation.\n" +
    "- No labels, no commentary.\n" +
    "- Preserve line breaks.\n" +
    "- Keep bullets as bullets.\n" +
    "- Do NOT omit or summarize any content.\n" +
    "\n" +
    "English:\n" +
    english
  );
}

async function* streamTextFromResponse(res: Response, signal?: AbortSignal): AsyncGenerator<string> {
  const contentType = res.headers.get("content-type") ?? "";
  if (contentType.includes("text/event-stream")) {
    for await (const chunk of streamSseTextParts(res, signal)) yield chunk;
    return;
  }

  if (!res.ok) {
    const msg = await res.text().catch(() => "");
    throw new Error(`http_${res.status}:${msg.slice(0, 160)}`);
  }

  const json = await res.json().catch(() => null);
  if (!json) return;

  // Reuse the same extractor for non-stream JSON.
  const text = extractSseTextParts(JSON.stringify(json));
  if (text) yield text;
}

async function* streamSseTextParts(response: Response, signal?: AbortSignal): AsyncGenerator<string> {
  if (!response.ok) {
    const msg = await response.text().catch(() => "");
    throw new Error(`http_${response.status}:${msg.slice(0, 160)}`);
  }

  const stream = response.body;
  if (!stream) return;

  const reader = stream.getReader();
  const decoder = new TextDecoder();

  let aborted = false;
  const abortError = () => new Error(String(signal?.reason ?? "aborted"));
  const onAbort = () => {
    aborted = true;
    try {
      reader.cancel();
    } catch {
      return;
    }
  };

  if (signal) {
    if (signal.aborted) onAbort();
    else signal.addEventListener("abort", onAbort, { once: true });
  }

  let buffer = "";

  const consume = function* () {
    while (true) {
      let sep = buffer.indexOf("\n\n");
      let advance = 2;
      if (sep === -1) {
        sep = buffer.indexOf("\r\n\r\n");
        advance = 4;
      }
      if (sep === -1) break;

      const chunk = buffer.slice(0, sep);
      buffer = buffer.slice(sep + advance);

      const lines = chunk.split(/\r?\n/);
      for (const line of lines) {
        const prefix = "data:";
        if (!line.startsWith(prefix)) continue;

        const data = line.slice(prefix.length).trim();
        if (!data) continue;
        if (data === "[DONE]") continue;

        const text = extractSseTextParts(data);
        if (text) yield text;
      }
    }
  };

  while (true) {
    if (aborted) throw abortError();
    const { done, value } = await reader.read();
    if (done) break;
    if (aborted) throw abortError();

    buffer += decoder.decode(value, { stream: true });
    for (const piece of consume()) yield piece;
  }

  if (aborted) throw abortError();
  buffer += "\n\n";
  for (const piece of consume()) yield piece;
}

function splitForTranslation(input: string): string[] {
  const text = input.replace(/\r\n/g, "\n").trim();
  if (!text) return [];

  const out: string[] = [];
  for (const para of text.split(/\n{2,}/g)) {
    const p = para.trim();
    if (!p) continue;

    const lines = p.split("\n");
    let buf: string[] = [];

    const flush = () => {
      const s = buf.join("\n").trim();
      if (s) out.push(s);
      buf = [];
    };

    for (const line of lines) {
      const l = line.trimEnd();
      if (/^\s*[-*•]\s+/.test(l) || /^\s*\d+\./.test(l)) {
        flush();
        out.push(l.trim());
      } else {
        buf.push(l);
      }
    }

    flush();
  }

  return out;
}

type StreamUpdate = {
  titleChunk?: string;
  bodyChunk?: string;
  done?: boolean;
};

async function translateViaOpenAIStream(
  cfg: ProviderConfig,
  input: string,
  signal?: AbortSignal
): Promise<AsyncGenerator<string>> {
  const { url, kind } = resolveOpenAIEndpoint(cfg.baseURL);
  const prompt = buildTranslationPrompt(input);

  const body =
    kind === "responses"
      ? {
          model: cfg.modelID,
          input: prompt,
          temperature: 0,
          max_output_tokens: 1024,
          stream: true,
        }
      : {
          model: cfg.modelID,
          messages: [
            { role: "system", content: "You are a translation engine." },
            { role: "user", content: prompt },
          ],
          temperature: 0,
          max_tokens: 1024,
          stream: true,
        };

  let res: Response;
  try {
    res = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "text/event-stream",
        Authorization: `Bearer ${cfg.apiKey}`,
      },
      body: JSON.stringify(body),
      signal,
    });
  } catch (e) {
    if (signal?.aborted) throw new Error(String(signal.reason ?? "aborted"));
    throw e;
  }

  async function* gen(): AsyncGenerator<string> {
    for await (const chunk of streamTextFromResponse(res, signal)) yield chunk;
  }

  return gen();
}

function renderTranslationBlock(body: string): string {
  const content = body.trim() ? body : "译文生成中…";
  return `${START_TAG}\n${content}\n${END_TAG}`;
}

function shouldFlush(lastFlush: number): boolean {
  return Date.now() - lastFlush >= 300;
}

async function translateReasoningStreamed(
  cfg: ProviderConfig,
  titleEn: string,
  bodyEn: string,
  onUpdate: (u: { titleCn?: string; bodyCn?: string; done?: boolean }) => void,
  signal?: AbortSignal
): Promise<void> {
  let titleCn = "";
  let bodyCn = "";

  let lastFlush = 0;

  const flush = (done?: boolean) => {
    onUpdate({
      titleCn: titleCn ? titleCn : "译文生成中…",
      bodyCn: bodyCn ? bodyCn : "译文生成中…",
      done,
    });
    lastFlush = Date.now();
  };

  flush(false);

  const titleTask = (async () => {
    if (!titleEn.trim()) return;

    const titleStream = await translateViaOpenAIStream(cfg, titleEn, signal);
    for await (const chunk of titleStream) {
      titleCn += chunk;
      if (shouldFlush(lastFlush)) flush(false);
    }
  })();

  const bodyTask = (async () => {
    if (!bodyEn.trim()) return;

    const segments = splitForTranslation(bodyEn);
    for (let i = 0; i < segments.length; i += 1) {
      const seg = segments[i];
      const segStream = await translateViaOpenAIStream(cfg, seg, signal);
      let segOut = "";
      for await (const chunk of segStream) {
        segOut += chunk;
        if (shouldFlush(lastFlush)) {
          const combined = (bodyCn + (bodyCn ? "\n\n" : "") + segOut).trim();
          onUpdate({ titleCn: titleCn ? titleCn : "译文生成中…", bodyCn: combined, done: false });
          lastFlush = Date.now();
        }
      }
      if (segOut.trim()) {
        bodyCn = (bodyCn + (bodyCn ? "\n\n" : "") + segOut.trim()).trim();
      }
      flush(false);
    }
  })();

  await Promise.all([titleTask, bodyTask]);
  flush(true);
}

export const ThinkTranslator: Plugin = async ({ directory, client }) => {
  const providerCfg = loadProviderConfigFromOpencodeConfig(directory);

  const patch = (client as unknown as {
    _client?: {
      patch?: (opts: { url: string; body?: unknown }) => Promise<PatchResult>;
    };
  })._client?.patch;

  const lastAssistantMessageBySession = new Map<string, string>();
  const reasoningByPartID = new Map<string, string>();
  const patchedPartIDs = new Set<string>();

  const pendingPatchByPartID = new Map<string, { part: PartEvent; text: string }>();
  const patchInFlightByPartID = new Set<string>();

  const MAX_CONCURRENT_TRANSLATIONS = 3;
  const MAX_RETRIES = 5;
  const FIRST_TOKEN_TIMEOUT_MS = 10_000;
  const TOTAL_TIMEOUT_MS = 120_000;

  type Task = {
    part: PartEvent;
    base: string;
    title: string;
    body: string;
    createdAt: number;
    attempt: number;
    generation: number;
  };

  const queue: Task[] = [];
  let running = 0;

  const flushPatch = (partID: string) => {
    if (!patch) return;
    if (patchInFlightByPartID.has(partID)) return;

    const pending = pendingPatchByPartID.get(partID);
    if (!pending) return;

    pendingPatchByPartID.delete(partID);
    patchInFlightByPartID.add(partID);

    const url = `/session/${pending.part.sessionID}/message/${pending.part.messageID}/part/${partID}`;
    const body = { ...pending.part, text: pending.text };

    let timer: ReturnType<typeof setTimeout> | undefined;
    const timeout = new Promise<never>((_r, reject) => {
      timer = setTimeout(() => reject(new Error("patch.timeout")), 2000);
    });

    Promise.race([patch({ url, body }), timeout])
      .catch(() => {
        return;
      })
      .finally(() => {
        if (timer) clearTimeout(timer);
        patchInFlightByPartID.delete(partID);
        if (pendingPatchByPartID.has(partID)) flushPatch(partID);
      });
  };

  const enqueuePatch = (p: PartEvent, updatedText: string) => {
    if (!patch) return;

    const prev = pendingPatchByPartID.get(p.id);
    if (prev && prev.text === updatedText) return;

    pendingPatchByPartID.set(p.id, { part: p, text: updatedText });
    flushPatch(p.id);
  };

  const runQueue = () => {
    while (running < MAX_CONCURRENT_TRANSLATIONS && queue.length) {
      const task = queue.shift()!;
      running += 1;

      const { part, base, title, body, createdAt } = task;

      const startAttempt = (attempt: number, generation: number) => {
        let sawFirstToken = false;
        const attemptStartedAt = Date.now();

        let titleCn = "";
        let bodyCn = "";
        let lastFlush = 0;

        const update = (u: { titleCn?: string; bodyCn?: string; done?: boolean }) => {
          const nextTitleCn = u.titleCn ?? (sawFirstToken ? titleCn : "译文生成中…");
          const nextBodyCn = u.bodyCn ?? (sawFirstToken ? bodyCn : "译文生成中…");

          const nextBase = applyTitleTranslation(base, nextTitleCn);
          const nextBlock = renderTranslationBlock(nextBodyCn);
          const nextText = nextBase + "\n\n" + nextBlock + "\n";
          enqueuePatch(part, nextText);

          if (typeof u.titleCn === "string") titleCn = u.titleCn;
          if (typeof u.bodyCn === "string") bodyCn = u.bodyCn;
        };

        const controller = new AbortController();
        const attemptSignal = controller.signal;

        let firstTokenTimer: ReturnType<typeof setTimeout> | undefined;
        firstTokenTimer = setTimeout(() => {
          if (sawFirstToken) return;
          controller.abort("first_token_timeout");
        }, FIRST_TOKEN_TIMEOUT_MS);

        const totalTimer = setTimeout(() => {
          controller.abort("total_timeout");
        }, TOTAL_TIMEOUT_MS);

        const onToken = () => {
          if (sawFirstToken) return;
          sawFirstToken = true;
          if (firstTokenTimer) {
            clearTimeout(firstTokenTimer);
            firstTokenTimer = undefined;
          }
        };

        const cleanupTimers = () => {
          if (firstTokenTimer) {
            clearTimeout(firstTokenTimer);
            firstTokenTimer = undefined;
          }
          clearTimeout(totalTimer);
        };

        const done = (finalTitle: string, finalBody: string) => {
          cleanupTimers();
          update({ titleCn: finalTitle, bodyCn: finalBody, done: true });
        };

        const fail = (msg: string) => {
          cleanupTimers();
          update({ titleCn: msg, bodyCn: msg, done: true });
        };

        (async () => {
          try {
            if (!providerCfg.baseURL.trim() || !providerCfg.apiKey.trim() || !providerCfg.modelID.trim()) {
              fail("未配置翻译接口(baseURL/apiKey/modelID)");
              return;
            }

            update({ titleCn: "译文生成中…", bodyCn: "译文生成中…", done: false });

            await translateReasoningStreamed(providerCfg, title, body, (u) => {
              if ((u.titleCn && u.titleCn.trim()) || (u.bodyCn && u.bodyCn.trim())) onToken();
              const now = Date.now();
              if (now - lastFlush < 300 && !u.done) return;
              lastFlush = now;
              update(u);
            }, attemptSignal);

            done(titleCn || "", bodyCn || "");
          } catch (e) {
            const elapsed = Date.now() - createdAt;
            const nextAttempt = attempt + 1;
            const err = String(e);

            if (elapsed >= TOTAL_TIMEOUT_MS || err.includes("total_timeout")) {
              fail("翻译超时");
              return;
            }

            if (nextAttempt <= MAX_RETRIES) {
              setTimeout(() => startAttempt(nextAttempt, generation + 1), 300 * nextAttempt);
              return;
            }

            if (err.includes("http_")) {
              const code = err.match(/http_(\d{3})/)?.[1] ?? "";
              fail(code ? `翻译失败(HTTP ${code})` : "翻译失败");
              return;
            }

            if (err.includes("first_token_timeout")) {
              fail("翻译无响应");
              return;
            }

            fail("翻译失败");
          } finally {
            cleanupTimers();
            running -= 1;
            runQueue();
          }
        })();
      };

      startAttempt(task.attempt, task.generation);
    }
  };

  const enqueueTranslateAndPatch = (p: PartEvent, base: string) => {
    const { title, body } = extractTitleAndBody(base);

    queue.push({
      part: p,
      base,
      title,
      body,
      createdAt: Date.now(),
      attempt: 1,
      generation: 1,
    });

    runQueue();
  };

  return {
    "experimental.chat.messages.transform": async (_input, output) => {
      for (const m of output.messages ?? []) {
        if (m.info.role !== "assistant") continue;

        for (const p of m.parts ?? []) {
          if (p.type !== "text" && p.type !== "reasoning") continue;
          if (typeof p.text !== "string") continue;

          p.text = stripTranslationBlocks(p.text);
          if (p.type === "reasoning") p.text = stripTitleTranslation(p.text);
        }
      }
    },

    event: async ({ event }) => {
      if (event.type === "message.updated") {
        const info = (event.properties as { info: Message }).info;
        if (info.role === "assistant") lastAssistantMessageBySession.set(info.sessionID, info.id);
        return;
      }

      if (event.type !== "message.part.updated") return;

      const { part, delta } = event.properties as { part: Part; delta?: string };
      const p = part as unknown as PartEvent;

      const expectedMessageID = lastAssistantMessageBySession.get(p.sessionID);
      if (!expectedMessageID || p.messageID !== expectedMessageID) return;

      if (patchedPartIDs.has(p.id)) return;

      if (p.type === "reasoning" && typeof delta === "string" && delta.length) {
        const prev = reasoningByPartID.get(p.id) ?? "";
        reasoningByPartID.set(p.id, prev + delta);
      }

      if (p.type !== "reasoning" || !p.time?.end) return;

      const full = reasoningByPartID.get(p.id) ?? p.text ?? "";
      reasoningByPartID.delete(p.id);

      const base = stripTranslationBlocks(full).trimEnd();
      if (!base) return;

      const pendingBase = applyTitleTranslation(base, "标题译文生成中…");
      const pendingBlock = buildTranslationBlockPending();
      const pendingText = pendingBase + "\n\n" + pendingBlock + "\n";

      patchedPartIDs.add(p.id);
      enqueuePatch(p, pendingText);

      enqueueTranslateAndPatch(p, base);
    },
  };
};


📌 转载信息
原作者:
Tongz
转载时间:
2026/1/23 15:34:53

浅谈 Opencode + Oh My Opencode 工作时的 SOP 与 Workflow

总述

在使用 Opencode 的过程中,开发效果的好坏,确实在很大程度上取决于模型本身的智能水平。模型越聪明,理解需求、补全上下文、处理复杂逻辑的能力就越强,这点毋庸置疑。

但现实情况往往没那么理想。不是每个人都能稳定使用 Claude 或 GPT 的最新模型;即便能用,在高强度调用、上下文越来越长的情况下,模型也很容易开始 “走神”—— 出现幻觉、理解跑偏,甚至一本正经地胡说八道。

当你发现模型开始反复犯低级错误,或者对同一个需求给出前后矛盾的实现方案时,问题往往不在你,而在于:
你默认模型 “应该懂”,但它其实已经跟不上了。

这时候,单纯追求更强的模型,并不能从根本上解决问题。真正能兜底的,是一套清晰、稳定、可复用的 SOP(Standard Operating Procedure),以及基于 SOP 形成的 Workflow。

简单说一句就是:

模型负责输出,人负责把路铺好。


为什么越熟练,反而越需要 SOP

很多人在刚接触 Opencode 时,会觉得 SOP 是个 “新手才需要的东西”。
等自己用顺了,就开始随意对话、即兴发挥,哪里卡了就往模型里一股脑儿丢。

这种方式在小脚本、一次性需求里问题不大,
但只要项目稍微复杂一点,SOP 缺失的后果就会逐渐显现:

  • 模型对当前任务的理解不稳定
  • 不同轮对话输出风格和实现思路不一致
  • 修复一个问题,引入两个新问题
  • 过几轮之后,连模型自己都 “忘了现在在干嘛”

SOP 的存在,并不是为了限制模型的能力,而是为了减少不必要的理解分支
你提前告诉它当前阶段该做什么、不该做什么,反而能让模型把有限的注意力用在真正重要的地方。

从这个角度看,SOP 不是 “给模型用的”,
而是给人省心用的


SOP 和 Workflow,本质上解决的是两个问题

这里需要稍微区分一下这两个概念。

  • SOP 更像是一组 “约定俗成的操作规则”
    比如:每次开始前先让模型复述目标、禁止未经确认的大范围重构、输出必须标注修改点等。
  • Workflow 则是你在实际工作中形成的一种节奏
    什么时候该拆需求,什么时候该写代码,什么时候该停下来做确认。

SOP 偏静态,Workflow 偏动态;
一个负责 “别乱来”,一个负责 “往前走”。

在 Opencode + Oh My Opencode 的组合里,这两者往往是一起生效的。


我的 SOP

说明

  • 以下 SOP 均由 GPT5.2 模型给出并由本人在使用过程中润色改造
  • 所有内容均为可直接复制使用的工程指令
  • 不包含分析、解释或背景说明
    • 在开始使用 SOP 前应当完成项目初始化,推荐使用 Opencode 官方指令 /init
  • SOP 为原子级,Workflow 为高频组合
  • 指令中 ulw 命令为 oh my opencode 插件特有指令,详情可以移步 oh-my-opencode/README.zh-cn.md at dev · code-yeongyu/oh-my-opencode

SOP-01:工程启动 / 陌生任务分析

ulw #start
首先完整阅读 AGENTS.md,不要跳过。

然后按以下顺序执行分析:
1) 扫描整个仓库结构,识别项目入口、核心模块、分层与边界
2) 定位与当前任务直接相关的模块、文件和入口点
3) 用 3–5 条要点总结当前实现:
   - 每条描述一个职责或核心逻辑
   - 关注数据流、控制流和隐含假设
4) 标出不清晰、强耦合或潜在高风险区域
5) 提出至少 2 种可行推进方案:
   - 每种方案包含:优点、缺点、主要风险
6) 选择风险最低的方案,并给出明确的执行计划

约束:
- 在完成以上步骤前,不要修改任何代码
- 不要提前实现或重构


SOP-02:仓库探索 / 代码定位

ulw #scan
@explore:

1) 自顶向下扫描仓库结构
2) 识别与目标主题相关的模块、包和目录
3) 梳理主要调用链和依赖方向
4) 标出变更影响最大的 3 个区域并说明原因

约束:
- 仅做分析
- 不给实现或修改建议


SOP-03:技术调研 / 最佳实践

ulw #research
@librarian:

1) 查找该主题的官方文档或权威推荐做法
2) 对比 2–3 种成熟方案
3) 总结每种方案的适用场景与风险
4) 输出可直接用于当前项目的结论

约束:
- 避免长引用
- 只输出可执行结论


SOP-04:架构 / 方案决策

ulw #design
@oracle:

1) 在当前项目约束下提出 2–3 种设计或架构方案
2) 对每种方案说明:
   - 适用前提
   - 主要风险
   - 对现有代码的侵入程度
3) 推荐最安全的方案并说明原因
4) 明确不推荐其他方案的理由


SOP-05-FE:前端功能开发(使用 ui-ux-pro-max)

ulw #dev-fe
本任务是前端功能开发,加载skill:ui-ux-pro-max并根据这个技能的指引来进行 UI/UX 设计。

第 0 步:需求与界面范围确认
1) 用 3–6 条要点明确功能边界与用户路径
2) 列出必须态与异常态(loading / empty / error / 权限 / 网络失败)
3) 明确接口依赖与字段使用方式

第 1 步:UI/UX 设计(ui-ux-pro-max)
1) 产出页面结构与组件拆分
2) 明确交互细节与反馈机制
3) 定义各状态下的 UI 行为
4) 考虑可用性与可访问性

第 2 步:实现计划
1) 明确组件、路由、状态管理与请求层改动点
2) 拆分为可运行的增量步骤

第 3 步:增量实现
- 每一步完成后项目必须可运行
- 每一步完成后汇报:
  - 修改的文件
  - 实现的可观察行为
  - 修改原因
  - 下一步计划

约束:
- 优先复用现有组件与样式
- 不做顺手重构
- 避免过度抽象

完成标准:
1) 最小自测清单(主流程 + 异常态 + 边界态)
2) 核心交互演示路径
3) 接口字段与错误处理记录


SOP-06-BE:后端功能开发

ulw #dev-be
本任务是后端功能开发。

第 0 步:业务与契约确认
1) 明确输入、输出、业务规则、权限与幂等性
2) 列出数据模型变更与迁移策略
3) 明确错误码与异常语义

第 1 步:API 契约设计
1) 定义 endpoint、method、参数与响应结构
2) 明确分页、排序、过滤规则
3) 明确鉴权与资源所有权校验

第 2 步:测试准备
1) 补充最小测试或复现用例
2) 覆盖正常、异常、权限与边界情况

第 3 步:增量实现
- 每一步完成后服务必须可运行
- 每一步完成后汇报:
  - 修改的文件
  - 可验证结果
  - 修改原因
  - 下一步计划

约束:
- 优先复用现有架构与库
- 不做顺手重构
- 数据变更必须可回滚

完成标准:
1) 最小验证步骤
2) API 契约摘要
3) 影响范围说明


SOP-07:前后端对接 / 联调

ulw #dev-integration
本任务是前后端对接与联调。

第 0 步:冻结接口契约
1) 输出统一接口契约
2) 明确字段类型、必填性、默认值、空值语义
3) 明确错误码与错误结构
4) 明确鉴权方式与过期策略

第 1 步:联调准备
1) 后端提供可联调环境或 Mock
2) 前端完成请求封装与校验
3) 明确接口版本与兼容策略

第 2 步:联调闭环
1) 走通主流程
2) 覆盖异常场景(权限、参数、5xx、超时、空数据)
3) 明确 loading、重试、降级与幂等策略

第 3 步:对账与验收
1) 对账字段、单位、精度、时区
2) 对账分页、排序与筛选边界
3) 对账权限与越权访问

每一步完成后汇报:
- 修改的前后端文件
- 已打通的路径
- 当前阻塞点
- 下一步计划

完成标准:
1) 联调验收清单
2) 最终接口契约摘要
3) 最小复现链路


SOP-08:高风险重构(分析)

ulw #refactor
在修改任何代码前:
1) 找出所有相关定义与引用
2) 区分公开 API 与内部实现
3) 评估影响范围、风险点与迁移顺序

约束:
- 未确认前不要修改代码


SOP-09:高风险重构(执行)

ulw #refactor-exec
1) 如缺少测试,先补充
2) 按迁移顺序逐步修改
3) 每一步完成后说明:
   - 本步修改内容
   - 安全性理由
   - 可运行证据
   - 下一步计划


SOP-10:Bug / 异常排查

ulw #debug
1) 列出最可能的 3 个根因并排序
2) 为每个根因提供最小验证方法
3) 只修复被验证的根因
4) 说明其他假设被排除的原因

完成后:
- 给出回归验证步骤


SOP-11:提交前自检

ulw #review
1) 列出修改的文件与关键差异
2) 指出潜在风险与边界情况
3) 检查是否符合 AGENTS.md
4) 给出回滚方案
5) 给出最小本地验证命令


SOP-12:任务闭环

ulw #wrap
1) 确认目标是否完成
2) 按文件或模块总结关键变更
3) 列出风险与覆盖情况
4) 给出验证步骤
5) 给出回滚方案
6) 建议 AGENTS.md 的补充(仅新增)


关于 #xxx 这类指令

#xxx 是什么?
#start#plan#review 这一类 #xxx,本质上只是给模型看的阶段标签
它们不参与具体内容,只用来告诉模型:现在处在什么阶段,该怎么配合你工作


为什么要这样做?
因为模型默认会把所有对话当成一整段连续上下文来处理。
一旦讨论过多、来回修改几次,它就很容易把已经不重要、甚至已经作废的内容继续当成前提。

#xxx,等于是提前告诉模型:

“接下来换个阶段思考,别按刚才那套逻辑继续往下跑。”


这么做的必要性是什么?
这类指令的意义不在于 “让模型更聪明”,而在于减少跑偏和误解

在任务切换频繁、上下文偏长的情况下,一个清晰的阶段标记,往往比多写几段解释更有效。
成本很低,但能明显提升稳定性,尤其适合长期、反复使用。


SOP 之外,更重要的是 Workflow 的 “手感”

有了 SOP,并不代表事情就会自动变顺。
真正拉开差距的,往往是你如何把这些规则串成一套顺手的流程

很多人 Workflow 混乱,并不是能力问题,而是节奏问题:

  • 需求还没想清楚,就开始让模型写代码
  • 代码刚能跑,又急着往上堆功能
  • 出问题时,直接整段推翻重来

相比之下,一个成熟的 Workflow 往往更 “慢”,但更稳:

  • 先对齐理解,再动手
  • 先最小可用,再逐步完善
  • 每一步都能随时停下来回滚或修正

Oh My Opencode 在这里扮演的角色,其实很清晰:
它不是帮你 “多写点代码”,而是帮你守住流程的下限


我的 Workflow

# 接手陌生项目
ulw #start #scan

# 标准新功能(前后端)
ulw #start
→ ulw #dev-be
→ ulw #dev-fe
→ ulw #dev-integration
→ ulw #review
→ ulw #wrap

# 仅前端功能
ulw #start
→ ulw #dev-fe
→ ulw #review
→ ulw #wrap

# 仅后端功能
ulw #start
→ ulw #dev-be
→ ulw #review
→ ulw #wrap

# Bug 修复
ulw #debug
→ ulw #review
→ ulw #wrap

# 高风险重构
ulw #start
→ ulw #refactor
→ ulw #refactor-exec
→ ulw #review
→ ulw #wrap

# 技术选型
ulw #research
→ ulw #design


把模型当成同事,而不是许愿池

最后一个很重要的心态转变是:
不要把模型当成全知全能的存在,而要把它当成一个执行力强、但容易跑偏的同事。

你需要做的不是 “多问”,而是:

  • 把问题拆清楚
  • 把边界讲明白
  • 在关键节点做确认
  • 发现偏离及时拉回来

SOP 和 Workflow,本质上就是你和模型之间的协作协议。


结语

模型会更新,能力会上涨,也可能随时被限流;
但一套成熟的 SOP 和顺手的 Workflow,会长期存在。

Opencode + Oh My Opencode 真正提供的,不是 “写代码的捷径”,
而是一种在不完美模型条件下,依然能稳定推进工作的方式

当模型不再可靠时,
流程,才是你最后的安全绳。


出处与使用说明

本文内容及其中的 SOP / Workflow 由 @HappyCoding 原创整理并在 LinuxDo 首发。
允许在保留原始出处的前提下复制、修改与二次分发,
不建议在未注明来源的情况下直接商用。


📌 转载信息
原作者:
HappyCoding
转载时间:
2026/1/23 10:25:18

0. 前言

Open code 的配置一直是比较繁琐的,虽然开箱即用,但如果想配置转发站就有点头疼了。在网上找了很多教程,但大都简略,很多第三方转发站的文档写的也不明白(点名)。

有很多佬友开发了可视化 / 后端插件,如 AI-toolboxOCCMCLIProxyAPI。这些都很好用!感谢佬友们!你们的贡献让社区变得如此美好,也深深激励了我

在使用了一段时间后,我发现掌握一些模型 config 配置原理是有必要的。插件只能帮忙切换 provider,每个模型的进阶参数如思考预算,仍然要自己配置。懂得如何修改配置文件也能让我们把这些插件用的更好.

所以写一篇教程,从 0 开始配置 opencode.json. 希望能帮到各位尝试 opencode(并想在 opencode 中配置使用自定义转发站)的佬友,同时也记录自己学习各类官方文档的过程

comment 1: 通过本教程你可以收获:
  • 通过截图和我一起配置文档一坨但富可敌国的转发站作为 Provider
  • 一起看懂模型接口,Opencode,AI-SDK 官方文档
  • 了解并动手设置 Claude 和 OAI 不同模型和接口的参数配置(如思考预算)
comment 2: 本教程重点在于详细和实操,不是一个面面俱到的教程.

篇幅所限,本篇教程大部分内容是折叠的!!!请点击黑色三角形展开查看!!!
篇幅所限,本篇教程大部分内容是折叠的!!!请点击黑色三角形展开查看!!!
篇幅所限,本篇教程大部分内容是折叠的!!!请点击黑色三角形展开查看!!!

1. 初始化

action 1:如果你已经有 opencode.json 就可以跳过

如果你没有安装 opencode:
请参考此链接安装 opencode TUI 版 Opencode|Github

如果你有 opencode:
查看在 C:\Users <你的用户名>.config\opencode 目录下是否存在 opencode.json
如果没有,请不要手动创建。经过第二节的 Provider 配置,它会自动产生在这个目录下。

2.Provider 配置

2.1 官方 Provider 配置

如果你使用的是 Opencode 官方支持的 Provider,例如 Opencode Zen,GLM Coding Plan,那么基本上只需要在安装好的 opencode TUI 中输入 /connect 并回车即可。

如果你想在这篇文档中找到含截图配置教学,请展开以下三角形

action 2:连接 Github Copilot 作为 Provider

打开 opencode,输入 /connect


选择 github copilot 或其他 official provider

按提示操作

恭喜你!验证成功! 现在可以通过 opencode 使用 copilot 或其他 official provider 了。

注意:只需要重新走一遍此流程就能覆盖之前的认证
Provider auth 的文件在这里:C:\Users <你的用户名>.local\share\opencode
如果对此流程具体涉及的文件感兴趣,拓展阅读在 2.2 节 action4 提供

reference 1:相关文档
  1. 官方文档 Providers | OpenCode
  2. 翻译版 提供商 (Providers) - OpenCode 中文文档

2.2 自定义 API Key / 转发站 Provider 配置

站内轮子:OpenCode 自定义服务商(中转站)接入指南

如果你想在这篇文档中找到含截图配置教学,请展开以下三角形

action 3:通过可视化方式连接 Right Code(转发站)作为 Provider(推荐)

如果使用 action3,最好也看一下 action4
以便于理解后续 Models 配置部分对.json 的自定义修改。

Opencode 的逻辑是由开发者来列出所有合法的 provider 供连接,非常全。但 Right Code 这样的转发站理论上确实不受 opencode 支持。

大部分佬友遇到的困难集中在使用 opencode 命令打开 TUI (Terminal UI) 界面后,/connect 的 provider 中没有 "other" 或者 "custom" 选项。我们可以看到 Other 选项是个标题,确实不能选 Other 选项,而且经过搜索 Other 里面也没有 Right Code 这样的转发站。

不过,我们仍然可以通过配置 opencode.json 来解决这个问题。
@coulsontl
使用佬友的项目 AI-toolbox 可视化配置 opencode.json 和 auth:

  • step 1:下载并安装 releases.

  • step 2:填写你想自定义的供应商名和显示名

  • step 3:按照图片填写 NPM 包和 BaseURL 即可

  • step 4: 点击 “获取模型”,在列表中添加想用的模型



  • step 5:已经可以使用了。Oh-My-Opencode 也可以配置此种模式导入的模型。


非常好用!感谢 coulsontl 佬友!

不支持自定义 Options/Variants 配置,导入新 Provider 会覆写旧文件

也许后续我会提 PR?

如想知道如何填写 NPM 包和 BaseURL,请参考 document-reading 1!

action 4:通过 /connect 方式连接 Right Code(转发站)作为 Provider

Opencode 的逻辑是由开发者来列出所有合法的 provider 供连接,非常全。但 Right Code 这样的转发站理论上确实不受 opencode 支持。大部分佬友遇到的困难集中在使用 opencode 命令打开 TUI (Terminal UI) 界面后,/connect 的 provider 中没有 "other" 或者 "custom" 选项。我们可以看到 Other 选项是个标题,确实不能选 Other 选项,而且经过搜索 Other 里面也没有 Right Code 这样的转发站。

TUI 中 /connect 选项设置基于默认的合法 auth。不过,我们只需要随意在没有连接的选项中选一个然后改成我们的 auth 就可以了。


这里选择 privatemode AI,随意填写一个 api key

随意选择一个模型

虽然看起来能用,但这显然这是不能用的。

接下来的才是重点:我们去修改这个 auth 为 Right Code 的 auth 和 model。

Provider auth 的文件在这里:C:\Users <你的用户名>.local\share\opencode
如果你之前连接过其他的方案,例如 github copilot,也会在这里显示。同时,你可以删除这里面的配置,TUI 里会同步更新,相信可能会有佬友配错了想 disconnected 但找不到路子的,请修改这个文件,删除对应部分即可。


我们可以看到这个文件中已经新增了刚才我们选择的 Provider privatemode AI 的 Auth,key 为 666。

然后,修改 provider name 为一个你喜欢的名字,我这里修改为 RC Codex,key 填写为你的 apikey。需要修改,否则自动识别,勿与 opencode 的内置 provider(例如刚才的 privatemode AI)同名。

(我这里填写 666 是为了占位,请在转发站生成 api key 并填写。下图为获取 api-key 的界面)

那么 auth 就配置完成了!
接下来请转到 C:\Users <你的用户名>.config\opencode
查看是否有 opencode.json 或.jsonc
我这里就没有。如果没有,创建一个。


可以把 auth.json 复制过来创建。如果右键创建,记得显示扩展名。

此处 jsonc 和 json 的区别是 jsonc 可以写批注。c 是 comment 的意思。其他没有区别。

创建完毕后,写入对应于 provider auth 的 model 内容,参考示例如下。具体需要配置的部分都进行了注释,将由 document-reading 1 来详细讲解,以便大家知道为什么这么配置,进而配置不同的转发站。

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "RC-Codex": {  // 和auth中修改的名称一致 "npm": "@ai-sdk/openai", // 兼容的SDK,将于后续document-reading 1详细讲解 "name": "RingCode-CodeX",  // 在 TUI 中显示的名称 "options": {
        "baseURL": "https://www.right.codes/codex/v1",  // 中转站 API 地址(必须以 /v1 结尾或符合 OpenAI 格式),将于document-reading 1中详细讲解 "apiKey": "666"// 明文填写你的api-key,懂得引用的佬友可以用引用形式 // 如果中转站需要自定义 headers,可添加: // "headers": { //   "X-Custom-Header": "your-value" // }
      },
      "models": {
        "gpt-5.1-codex-max": {  // 中转站支持的模型ID,例如 gpt-4o、claude-3-5-sonnet 等,将于document-reading 1中详细讲解 "name": "Codex-5.1-max(RC-Codex)" //自定义显示名称
        },
        "gpt-5.1": {
          "name": "Gpt-5.1(RC-Codex)"
        }
        // 添加更多模型...
      }
    }
  }
}

此 opencode-config.json 参考来源

- document-reading 1
- 官方文档:https://opencode.ai/docs/models/
- 站内轮子:https://linux.do/t/topic/1329050 

配置完成后,我们就可以愉快地重启 opencode,选择 codex 模型了。


什么…? 你用的不是 Right Code 不知道注释的地方填什么?你需要阅读官方文档!

请在document-reading 1和我一起阅读文档!
来看看我是怎么在Right Code文档完全一坨的前提下填写这些字段的。
(声明:Right Code是我最喜欢的转发站之一,codex量大管饱,没有不喜欢的意思,但是文档和网站确实需要改进)
action 5:通过 auth login 方式连接 Right Code(转发站)作为 Provider

通过在 powershell 中输入 opencode auth login 配置
TUI 的打开命令是 opencode,那么其实 opencode 是一个程序,TUI 其实就是无参运行这个程序。可以想象,其实带参运行这个程序是才是触发程序中内置的功能比较常见的途径。那么 /connect 对应的带参运行其实就是 opencode auth login.


输入 opencode auth login 后,可以自由键入 provider 名字然后 enter,找不到也没事,这是区别于(1)选项重要的一点。
其余选项,对于文件的修改都相同,请参考 action 4。
注意起名勿与 opencode 的内置 provider 同名。
document-reading 1:通过阅读 Opencode 和转发站官方文档配置 Provider

RightCode 的文档和网站是最草率的那一批,但我们仍然可以获得足够的信息。

  • step1:获得转发站 baseurl 以填写 baseurl 字段
    在 RightCode 官方文档中没有提到 Opencode 如何配置,也没写 BaseUrl。
    在模型列表可用端口处点击复制按钮,
    分别得到 https://www.right.codes/claude-aws
    https://www.right.codes/codex,那么加上 v1,这就是两个模型池分别的 BaseUrl。

    为什么加上 v1:
    RightCode 文档里也加了。v1 是通用的接口规范,如果接口请求不对,没有 v1 就加上 v1

    通过以上信息可以填写如下的 baseurl 和 apikey 部分(apikey 获取此处省略)
 "options": { "baseURL": "https://www.right.codes/codex/v1", // 中转站 API 地址(必须以 /v1 结尾或符合 OpenAI 格式),将于document-reading 1中详细讲解 "apiKey": "666"// 明文填写你的api-key,懂得引用的佬友可以用引用形式 // 如果中转站需要自定义 headers,可添加: // "headers": { //   "X-Custom-Header": "your-value" // } }, 
  • step2:填写 models
    在模型广场里找到了如下的模型

    在使用文档也里找到了如下的 models 可以填写。

    因此可以填写 config 文件中对应的这几行。(注意:实际支持 5.2codex,以模型广场为准。这文档不全)
 "models": { "gpt-5.1-codex-max": { // 中转站支持的模型ID,例如 gpt-4o、claude-3-5-sonnet 等,将于document-reading 1中详细讲解 "name": "Codex-5.1-max(RC-Codex)" //自定义显示名称 }, "gpt-5.1": { "name": "Gpt-5.1(RC-Codex)" } 
  • step3:获得转发站支持的接口以填写 npm 字段
    我们可以看到 Right Code 支持了 Openai 新版 responses 和老版 chat/completions 接口,也支持了 Antropic 新版 messages 接口。

    那么知道了支持的接口该怎么填写 npm 字段呢?这个字段好像也不是接口。

不知道什么意思的时候就请查看 Opencode 官方文档。

根据 Models | OpenCode opencode 是基于 Vercel 的 AI SDK 项目(实际上大部分 AI 工具都基于 AI-SDK 转发请求)和 models.dev 项目的。所以去查看这两个项目的官方文档。models.dev 看了之后和我们的需求没什么关系,opencode.json 的实现是依赖于 AI-SDK 的。AI-SDK 表示接口的实现依赖于对应的 npm 包。也就是说一个包对应一个接口。

查 AI SDK 的文档 Foundations: Providers and Models

进去之后发现左上角 navigator 有一个 Providers,那么层级应该是 Provider/Model,所以应该要从这里进去了。

点进去以后来到了这个页面

AI SDK Providers

分别查看 openai,openai-compatible-providers 和 claude 的接口标准

AI SDK Providers: OpenAI
AI SDK Providers: Anthropic
OpenAI Compatible Providers

能看到文档中说明 openai Provider 包含有 /responses 请求接口的实现,openai-compatible-providers 包含 /chat/completions 接口的实现,claude 包含有 /messages 接口的实现。这就对应上了。

因此我们可以配置接口部分:
"npm": "@ai-sdk/openai", // responses接口对应的SDK
由于官方文档中提到,Right Code 也支持了老版的 /chat/completions 接口,所以这个地方的 SDK 也可以配置为 openai-compatible.
`“npm”: “@ai-sdk/openai-compatible”, // 兼容的 SDK

完整的初步配置文件:


{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "RC-Codex": {  // 和auth中修改的名称一致 "npm": "@ai-sdk/openai", // /responses接口对应的SDK "name": "RingCode-CodeX",  // 在 TUI 中显示的名称 "options": {
        "baseURL": "https://www.right.codes/codex/v1", 
        "apiKey": "666"

      },
      "models": {
        "gpt-5.1-codex-max": {  // 中转站支持的模型ID "name": "Codex-5.1-max(RC-Codex)" //自定义显示名称
        },
        "gpt-5.1": {
          "name": "Gpt-5.1(RC-Codex)"
        }
      }
    }
  }
}

现在就可以用了。在后续 Options 中将提及如何优化模型体验,包括设置思考预算。

recommend 1:Provider 可视化配置,一键切换和分流

有很多佬友开发了可视化 / 后端插件

  • AI-toolbox:这款软件支持插件的装载和取消装载,对于我这种不想每次都用 Oh-My-Opencode 的人来说非常好。我非常喜欢这款软件!谢谢佬友!
  • OCCM :非常好用的一款小软件
  • CLIProxyAPI教程帖
  • CC Switch 将于近日发布的 3.10 版本支持 Opencode! commit 记录
  • 其他我没有关注到的链接非常抱歉!请留言!
reference 2:相关文档

1. 官方文档 Providers | OpenCode
2. 翻译版 提供商 (Providers) - OpenCode 中文文档

comment 3:碎碎念

@coulsontl 佬友的 AI-toolbox 很好!!但也有一些还没实现的部分:
①不含 Skills 设置
②目前只能设置 Oh-My-Opencode 插件中 Agents 的开关,不含 Custom Agents 的设置

其实我感觉 OMO 太重了,而且大多数项目其实并不完全适合 OMO 的 Agents 分配方式。要是 OMO 能完全嵌入 Opencode 的原生 Agents 流程就好了!

目前只是偶尔用用 OMO,日常开发更喜欢在 Opencode 中使用各种自定义 Agents,后续或许写一个 PR 来实现不同 custom agents 的开关,以及更上一层不同 Agents 组合配置的切换功能。

3.Models 配置

3.1 Options 配置

配好了转发站,但是模型用起来不好用,可能是因为没有配置 OptionsOptions 是厂商自定义的参数列表,例如 Codex 的思考预算,佬友们常说的 Xhigh / high 都在 options 设置。

3.1.1 Options 作用

(以 Right Code 转发站的 Codex 端点为例展示)

example 1:使用 Right Code 转发站转发 /codex 端点,默认配置

可以看到,虽然能返回,但 codex-max 在每次调用后马上就返回了,也没有展示思考,使用体验很不好。难道 codex-max 这么蠢吗?或者是被降智了?

example 2:使用 Right Code 转发站转发 /codex 端点,增加 Options 配置

但是如 example 2 所示,同样的模型和转发站,没有一步一停,也展示了思考内容,花费 2 分钟。

example 3:config 片段对比

在例图 1 时,我的 opencode.json 配置对应片段如下:

"RightCodes-OAI": {
      "npm": "@ai-sdk/openai",
      "name": "Rightcodes-OAI",
      "options": {
        "baseURL": "https://www.right.codes/codex/v1",
        "apiKey": "用你的api-key代替",
      },
      "models": {
        "gpt-5.1-codex-max": {
          "name": "gpt-5.1-codex-max",
        }
      }
    }

在例图 2 时,我的 opencode.json 配置对应片段如下:

"RightCodes-OAI": {
      "npm": "@ai-sdk/openai",
      "name": "Rightcodes-OAI",
      "options": {
        "baseURL": "https://www.right.codes/codex/v1",
        "apiKey": "用你的api-key代替",
      },
      "models": {
        "gpt-5.1-codex-max": {
          "name": "gpt-5.1-codex-max",
          "options":{
            "reasoningEffort": "high",
            "textVerbosity": "medium",
            "reasoningSummary": "auto"
	        }
        },
      }
    }

可以看出,问题的关键在于我在 options 中配置了三个选项 reasoningEffort:high,textVerbosity:medium和reasoningSummary:auto

additional 1:参数作用说明

reasoningEffort 代表模型的推理预算,从 low 到 Xhigh(一般推荐设置为 high,因为 Xhigh 可能并没有太明显的帮助,而且有些模型没有 Xhigh 选项)

textVerbosity 控制模型的输出长度,medium 代表中等;

reasoningSummary 代表模型是否输出 thinking 的总结(即 example2 中黄色的 thinking:xxx 部分),auto 代表自动控制思考总结的长度。

3.1.2 Options 配置

document-reading 2:通过阅读 OpenCode 官方文档配置 codex 模型和 claude 模型 options

这种时候我们要寻求 Opencode 官方文档的帮助。

  • step1:
    首先根据 Models | OpenCode opencode 是基于 Vercel 的 AI SDK 项目和 models.dev 项目的,所以去查看这两个项目的官方文档。

  • step2:
    查 AI SDK 的文档 Foundations: Providers and Models
    进去之后发现左上角 navigator 有一个 Providers,那么层级应该是 Provider/Model/Options,所以应该要从这里进去了。

  • step3:
    点进去以后来到了这个页面
    AI SDK Providers
    分别查看 openai 和 claude 的接口标准
    (为什么查看这两个接口请参考 notification 1&2):
    AI SDK Providers: OpenAI
    AI SDK Providers: Anthropic
    能看到文档中已经详细说明了每个 options 有什么含义及如何填写。


notification 1:Options 跟随 Models 变化,而不是 Provider 或中转站

options 配置隶属于 models 配置,不同 Provider 提供的请求接口不同,options 的设置也因此完全不同,例如 codex 系列模型通过 OPENAI 的 /responses 接口请求,claude 系列模型则通过 ANTROPIC 的 /messages 接口请求,因此,一个中转站的不同 Provider 模型应该遵循不同的 options 设置。

进一步地,同一个 Provider(厂商),相同接口的不同模型也可能支持不同的 options 设置,例如 codex 的部分模型虽然也使用 openai/responses 接口,但却不支持思考预算为 xhigh 的设置;gemini 的生图模型和推理模型显然不能遵循同样的 options 设置。

因此,最好的方式是为每个常用模型分别设置 options。

具体请参考 document-reading 1 中 AI-SDK 不同 Providers 页面下的相关接口实现。
分辨你的常用模型使用哪个接口,并确定其是否满足接口的所有 options 参数。
设置无效的 options 不会引发报错,但可能毫无作用,进而可能导致 example 1 中模型不能发挥出应有实力的表现。

document-reading 3:通过阅读 OPENAI 官方文档进一步配置 codex 模型 options

AI-SDK 的文档当然已经非常够用了,不过如果你想更深程度的了解究竟有哪些 options,那么 AI-SDK 的文档里还是没有写的完全清楚,例如在 include 这里,AI-SDK 只写了两个支持的值,实际上这里不止两个值可以填。

其实我们还可以去检查 OPENAI 和 ANTROPIC 的官方文档来确认究竟有哪些 options。

这里以 OPENAI 为例:我们查找官方文档中关于 Reponses API 的部分(善用文档库自带的搜索框)可以看到 include 列表一共有七个可选的值。

有任何关于你想要的模型自主设置也可以继续在 responses api 里探索。responses api 是许多模型共用的接口,你使用的模型可能并不支持该接口的所有功能。
https://platform.openai.com/docs/api-reference/responses/create

notification 2:不同中转站实现的接口可能不同

本文以配置 Right Code 家的 codex 和 claude 为例。在 RightCode 官方文档 中,codex 实现的接口为 OPENAI 官方的 responses 接口,因此应该遵循 responses 接口的 options 设置。claude 实现的接口为 ANTROPIC 官方的 messages 接口,因此应该遵循 messages 接口的 options 设置。

3.2 Variants 配置

Variants 实际上就是一个 List,其中的每个元素是一个 Options 设置。通过切换不同的 Variants,我们实际上能够切换不同的 Options 组合。参考文档:Models | OpenCode

原配置一个模型只能有一个设置:

 "gpt-5.2-codex": { "name": "gpt-5.2-codex", "options":{ "reasoningEffort": "high", "textVerbosity": "medium", "reasoningSummary": "auto" } }, 

现在我想针对这个模型切换不同的 Options 组合,则把它们打包到 Variants 列表里.

 "gpt-5.2-codex": { "name": "gpt-5.2-codex", "variants": { "high": { "reasoningEffort": "high", "textVerbosity": "low", }, "xhigh": { "reasoningEffort": "xhigh", "textVerbosity": "medium", }, }, }, 

可以看到,现在可以调整 xhigh 和 high 进行推理了.

4. 留言

Plugins 配置,如 Oh-My-Opencode 配置等站内已有其他轮子,不再赘述;

exercise 1:完善 Right Code/Claude 4.5 Opus 模型的初始 opencode.json 文件

题目:

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "RCS-Claude": {
      "npm": "@ai-sdk/anthropic",
      "name": "RC中转站Claude端点(awsq)",
      "options": {
        "baseURL": "https://www.right.codes/claude-aws/v1",
        "apiKey": "666"
      },
      "models": {
        "claude-opus-4-5-20251101": {
          "name": "claude-opus-4-5-20251101"
        }
      }
    }
  },
  "model": "RCS-Codex/gpt-5.1",
  "small_model": "opencode/grok-code"
}

答案供参考:

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "RCS-Claude": {
      "npm": "@ai-sdk/anthropic",
      "name": "RC中转站Claude端点(awsq)",
      "options": {
        "baseURL": "https://www.right.codes/claude-aws/v1",
        "apiKey": "666"
      },
      "models": {
        "claude-opus-4-5-20251101": {
          "name": "claude-opus-4-5-20251101",
           "variants":{ 
              "very-lazy": {
                "effort": "low" 
              },
              "think-2000":{
                "thinking": { "type": "enabled", "budgetTokens": 2000 },
              },
              "think-1w6":{
                "thinking": { "type": "enabled", "budgetTokens": 16000 },
              },
            }
        }
      }
    }
  },
  "model": "RCS-Codex/gpt-5.1",
  "small_model": "opencode/grok-code"
}

参考文档:
AI-SDK Provider:Antropic


Antropic Message 接口官方文档

不兼容的 options 不会生效,但问题在于也不会报错。它们只是影响你的模型 "智力".

习题 1 参考答案效果展示:opus4.5 very-lazy(不进行思考,努力值为低,即 “降智”)

努力值不设置时默认为高

习题 1 参考答案效果展示:opus4.5 think-2000:(思考预算为 2000)

习题 1 参考答案效果展示:opus4.5 think-16000:(思考预算为 16000)

同一个模型显示出三种不同的效果,展示了 options 配置的必要性.

感谢你看到这里!


📌 转载信息
转载时间:
2026/1/22 13:16:46

OpenCode GUI - VSCode 扩展版

基于哈雷佬的 Claudix 改造,将 OpenCode 带入 VSCode 侧边栏。

一句话介绍

在 VSCode 里直接使用 OpenCode,自动连接本地 server。

核心功能

  • 侧边栏聊天 - 复用 Claudix 的精美 UI,在 VSCode 侧边栏直接对话
  • 自动启动 - 配置本地地址后,扩展自动拉起 opencode serve
  • 快速上下文 - Ctrl+L 发送选中代码,右键菜单添加文件
  • 配置管理 - 设置页可视化编辑 OpenCode 配置(含 oh-my-opencode)

开发状态

早期版本,未经深度测试! 可能存在各种 bug,欢迎试用并在 Issues 反馈问题。

安装

下载 最新 Release,在 VSCode 中:扩展 → “从 VSIX 安装…”

前置要求

项目地址

致谢

感谢 @Haleclipse 提供的 Claudix UI 基础


📌 转载信息
原作者:
luxlzz
转载时间:
2026/1/22 13:14:56

佬们,做了个 agent 管理监控工具,c++ 开发,目前只支持 mac,agent 支持 opencode、claude code 和 codex 感兴趣可以看看



📌 转载信息
转载时间:
2026/1/21 22:25:51

背景

使用 CPA 作为 API 代理,配合 OpenCode,想要通过 OpenAI 兼容格式调用 Gemini 3
系列模型。

环境配置:

  • CPA 部署在自用云服务器,配置了 OpenAI 兼容转发
  • 上游服务:https://***.com(支持多模型的 API 服务|newapi)
  • 本地客户端:OpenCode


问题现象

  1. 第一个错误:404 Not Found
  • POST “/v1/models/gemini-3-flash-preview:streamGenerateContent?alt=sse” 404

  • 请求直接返回 404,模型无法调用。

  1. 第二个错误:Invalid Role
  • 修复 404 后,出现新错误:

  • Invalid param: Please use a valid role: user, model.


问题分析

  1. 404 问题分析
  • 查看 CPA 日志发现请求路径是:/v1/models/gemini-3-flash-preview:streamGenerateContent?alt=sse; 这是 Gemini 原生 API 格式,而不是 OpenAI 兼容格式 (/v1/chat/completions)。

  • 原因:OpenCode 检测到模型名包含 gemini 关键字,自动切换到 Gemini 原生协议。

  1. Invalid Role 问题分析
  • 修复 404 后,查看日志发现请求走的是:POST “/v1/responses” 400; 这是 OpenAI 新的 Responses API(用于 GPT-5.x 等新模型),而不是传统的 /v1/chat/completions。

  • 原因:OpenCode 配置中 openai provider 包含 reasoningEffort 等参数,触发了新 API 格式。而 Responses API 使用 role: “assistant”,Gemini API 期望 role:“model”,上游服务没有正确转换角色名。


解决方案

  1. 步骤 1:CPA 配置 - 给 Gemini 模型设置别名

修改服务器上的 /path/to/cpa/config.yaml,给 Gemini 模型设置不含 “gemini” 关键字的别名:

 openai-compatibility: - name: claude base-url: https://***.com/v1 api-key-entries: - api-key: sk-xxxxx models: # Claude 模型 - name: claude-opus-4-5-20251101 alias: claude-opus-4-5 # Gemini 模型 - 使用不含 "gemini" 的别名 - name: gemini-3-flash-preview alias: "g3-flash-preview" - name: gemini-3-pro-preview alias: "g3-pro-preview" - name: gemini-3-pro-image-preview alias: "g3-pro-image-preview" 

重启 CPA 容器:
docker restart cpa

  1. 步骤 2:OpenCode 配置 - 创建独立 Provider
    关键点:不要把 Gemini 模型放在带有 reasoningEffort 等参数的 openai provider
    下,否则会触发 Responses API。
    创建一个独立的 provider:
{ "provider": { "openai": { "name": "OpenAI", "options": { "baseURL": "https://***.com/v1", "apiKey": "your-key", "reasoningEffort": "medium" // 这会触发 Responses API }, "models": { "gpt-5.2": { ... } // GPT 模型放这里 } }, "openai-compat": { "name": "Gemini via CPA", "options": { "baseURL": "https://***.com/v1", "apiKey": "your-key" // 注意:不要加 reasoningEffort 等参数! }, "models": { "g3-pro-preview": { "name": "Gemini 3 Pro Preview", "limit": { "context": 1048576, "output": 65535 } }, "g3-flash-preview": { "name": "Gemini 3 Flash Preview", "limit": { "context": 1048576, "output": 65535 } }, "g3-pro-image-preview": { "name": "Gemini 3 Pro Image Preview", "limit": { "context": 1048576, "output": 65536 } } } } } } 


问题总结

问题原因解决方案
404 错误模型名含 gemini,OpenCode 自动用 Gemini 原生协议设置不含 gemini 的别名
Invalid RolereasoningEffort 参数触发 Responses API,角色名不兼容创建独立 provider,不加 reasoning 参数


关键

  1. 模型名称很重要:某些客户端会根据模型名自动选择协议,避免使用原厂商关键字(如 gemini、claude)可以强制走 OpenAI 兼容格式。
  2. OpenAI API 有两套格式:
    • 传统:/v1/chat/completions
    • 新版:/v1/responses(GPT-5.x,带 reasoning 功能)

自用经验仅供参考


📌 转载信息
转载时间:
2026/1/21 21:42:07

OpenCode + SVG:一套省心可控的 AI PPT 生成方案

前两天接到个活儿,要做个项目方案演示 PPT。

打开 PowerPoint 的那一刻,我盯着空白页面发了五分钟呆。

说实话,作为一个产品,PPT 能力属实一般 —— 内容我能写,但怎么让它好看有重点一眼能抓住人 ,这事儿我真不太行。

正好最近 OpenCode 特别火,号称是 Claude Code 的平替。最关键的是,它支持接入 GitHub Copilot 作为模型能力。

巧了,公司正好给订阅了 Copilot 企业版

也就是说,我现在直接实现了 API 自由 —— 装个 OpenCode,切到 Copilot,然后猛猛造就完事儿了。

于是就有了这篇:用 OpenCode 无痛生成可编辑 PPT 的完整流程


预期管理

先说清楚,咱们今天分享的这套方案,主打三个字:

  • 简单实用 (不需要 mcp、skills)
  • 复用性强 (掌握方法后可在多种 PPT 场景下使用)
  • 可控性高 (支持持续性的调整和二次修改)

最关键的是 —— 输出的内容可以在 PPT 里二次编辑 ,不像 NotebookLM 那种,生成出来是张图片,改都没法改。

但有一点要提前说:这次主打实用,美观性这种主观因素先不深究。不过可以保证,出来的效果直接拿去用是没问题的。

还有,我下面描述的操作流程更多是提供一种思路 ,很多步骤不是固定的,也没有所谓「一键出成果」的操作。希望大伙儿按这个思路多试试,换不同风格玩玩看。


省流总结

凡是需要把复杂信息结构化呈现、用图形辅助理解的场景,都很契合这套流程。

熟悉 Vibe Coding 的小伙伴,看完这个流程估计不用看细节就能直接上手了:

  1. 安装 OpenCode
  2. 安装官方插件 oh-my-opencode
  3. 创建项目文件夹,准备好 PPT 文稿内容
  4. 用 OpenCode 打开项目文件夹,切换到 Plan 模式,输入 ulw 进入 Ultrawork 模式
  5. 输入下文准备好的 Prompt,选择 PPT 风格,输出为 SVG 格式
  6. 在 PPT 中导入 SVG 文件,点击「转换为形状」
  7. 微调一下文本排列和字号,搞定!

效果展示

正好这两天国外 X 上有位大佬发了篇长文特别火:「如何在一天内彻底修复你的人生」,我就拿这篇文章来做流程演示。

这是从一篇 4000 字的长文,到一套可编辑的 PPT 录屏效果。

全程用 OpenCode 生成,导入后可以直接在 PPT 里改,先瞅瞅看效果如何

原文链接:https://x.com/thedankoe/status/2010751592346030461?s=20

如何在一天内彻底修复你的人生.pdf
想上传视频的,没找到咋搞,大伙儿打开 PDF 看看也行。

前期准备

1. 安装 OpenCode

打开官网,安装指引很详细。支持三种方式:终端、客户端、IDE 插件。

个人推荐直接选客户端 —— 方便查看历史记录,使用门槛也低。

官方安装链接:OpenCode | Download


2. 添加模型提供商

安装完成后,打开终端输入以下命令,选择 Agent 执行任务时用哪个模型提供商:

opencode auth login

我这里直接选了 GitHub Copilot。

没有提前准备 API 也没关系 —— 官方很贴心地内置了几个免费模型,终端里选第一项,或者客户端里选置顶的几个大模型,就能直接免费用。

备注:以下所有效果均通过 GitHub Copilot 的 Claude Opus 4.5 模型生成。



3. 安装 oh-my-opencode 插件

这一步很关键。

oh-my-opencode 是官方插件,一个强大的 OpenCode 扩展集合。简单说,开启后会进入火力全开模式 ,自动根据任务情况安排最合适的工作 Agent。

安装很简单,直接在对话框输入:

"Install and configure by following the instructions here https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/refs/heads/master/README.md" 

参考文档:🔥 Oh My OpenCode (Oh My OpenCode) - OpenCode 中文文档


4. 准备项目文件

新建一个文件夹作为项目文件夹,把 PPT 文稿内容存成 .md 格式放进去,然后用 OpenCode 客户端打开这个文件夹。

到这里,所有前置准备就完成了。接下来进入 PPT 生成的具体步骤

PPT 生成操作流程

1. 进入 Ultrawork 模式

打开项目文件后,在输入框直接输入 ulw,让 OpenCode 进入「燃起来」的 Ultrawork 模式

输入后,会先告知该模式的一些事项和规则。官方给出了详细的 Prompt,最下方也有使用场景说明:

  • 探索代理(背景)— 代码库结构、文件模式、内部实现
  • 图书管理员代理(背景)— 外部文档、API 参考、开源示例
  • 计划代理 — 详细工作分解和策略
  • 数据库代理 — 架构决策、代码审查、高智商推理
  • 前端 UI/UX 工程师 — 视觉设计和实现
  • 文档编写者 — 技术文档


2. 分析 PPT 内容

接着,把工作模式切换成 Planner-Sisyphus—— 这是 OpenCode 的计划只读模式 ,专门用于自我演进、迭代优化和处理高复杂度任务的规划模式。

然后通过 @ 符号引用 PPT 文案的 md 文件,在输入框输入:

请作为一名资深 PPT 设计师,帮我处理这份文档: 

1. **拆解**:提炼文档精华,产出结构化的 PPT 页面清单(包含页数、内容、重点)。 

2. **渲染**:利用 SVG 矢量代码输出每一页的视觉雏形。要求:图文分离、层级分明,代码需兼容 PPT 的"转换为形状"功能,以便我进行后期可编辑式的二次调整。  

基于这些诉求,给出方案。

这一步其实不需要什么精妙的提示词。项目初期,每次对话更多是想法碰撞和方案定位 ,不是一开始就用提示词框死大模型的想象空间。

直接在 Plan 模式下抛出核心诉求:一是 拆解,二是 渲染。然后让大模型输出方案,根据提示不断调整方向,最终达到效果即可。

只要能出满意的效果,那这个提示词就是合理且有效的。

按这个思路,大模型输出的方案大概率包括:

  • 拆解后每页 PPT 的内容(标题、内容、重点…)
  • PPT 输出模式确认(分页生成 / 全部生成)
  • 设计规范确认(尺寸、配色、字体、风格…)
  • SVG 输出格式要求(代码规范、组件规范…)

如果觉得拆解不合适,可以在 Planner 模式下持续对话,不断提要求,直到满意为止。


3. PPT 风格确认

这一步,建议只给定 尺寸要求、配色要求、主题色要求,剩下的布局排列和组件样式,推荐先让大模型自由发挥 —— 体验一下抽盲盒的快乐。

可以参考我的输入试试,这里的配色是直接从公司 PPT 模板里扣的,大伙儿可以替换成自己的要求:

尺寸要求:16:9
主题风格:浅色风格
配色要求:
| 颜色用途 | RGB 值 | 说明 |
|---------|--------|------|
| 主色 | `rgb(1,107,255)` | 品牌蓝 |
| 次色 | `rgb(86,91,255)` | 紫蓝 |
| 辅助色 | `rgb(46,204,247)` | 青蓝 |
| 背景色 | `rgb(246,246,246)` | 浅灰 |
| 文字色 | `rgb(0,0,0)` | 黑色 |

先让大模型输出前三页看看效果。下面是第二、三页的效果 —— 会发现实在太寡淡、太平面化了,不是不能用,就是一眼看上去没有记忆点。

这时我想到:好的 PPT 绝不是套用模板,而是要根据内容进行「适配性设计」,才能真正突出重点、制造记忆点。

既然如此,能不能让大模型先理解文本,由它来构思最契合的风格和布局方案?

于是直接输入了一个简单的要求:

基于这篇文章的内容,和品牌配色,还可以有怎样的风格和布局推荐?

结果很 amazing!

OpenCode 内置的 Agent 能力非常强大 —— 不仅分析出这篇文章的主题与「成长」「身份重塑」「行为心理学」「自我发展」等关键词相关,还会根据这些关键词在网上搜索相关的 PPT 设计方案,最终给出一个非常详细的每页风格推荐。

根据新的设计方案,第三页推荐使用「冰山隐喻」风格,效果如下 —— 确实比第一版好太多了!

接着,只需要根据设计方案,对剩下的页面做批量生成即可。


4. PPT 内容编辑

SVG 批量生成后,难免有些文本内容或布局效果不符合预期。怎么办?

最简单的办法:直接在对话框描述问题,还可以通过 辅助说明需要调整的地方。

如果只是文本内容的问题,也可以用 VSCode 打开 SVG 文件,直接手动改。


5. PPT 文件导入

最后一步。

检查所有 SVG 效果图都满足要求后,把 SVG 文件导入 PPT,点击「转换为形状」,就能把 SVG 一键转成 PPT 支持的组件样式,对所有元素进行编辑和调整。

这样再也不用担心大模型生成的 PPT 是一次性的 —— 上下文丢了,都不知道怎么改。

具体的导入和转化操作可以参考我往期的内容:

实际操作中,转化后的效果还是会遇到不少问题:字号错乱、布局偏移、组件变形…

这里简单总结了一些优化方案,只需要在输出 SVG 前,把要求全部告知大模型即可:

1️⃣ **圆角矩形优化**:使用 `<path>` + 贝塞尔曲线 `C` 命令绘制,避免 `<rect rx="24">` 在 PPT 中丢失圆角

2️⃣ **字体优化**:Windows 字体优先排列 `Microsoft YaHei, SimHei, PingFang SC, sans-serif`,避免 PingFang SC 在 Windows 上不存在导致布局变化

3️⃣ **文字定位优化**:使用 `style` 属性整合样式,手动计算居中位置,避免 `text-anchor``dominant-baseline` 属性支持不完善

4️⃣ **颜色格式优化**:使用 `#RRGGBB` + `fill-opacity` 分离透明度,避免 `rgba()` 和带透明度的十六进制颜色支持不佳

5️⃣ **阴影效果**:移除 `filter="drop-shadow(...)"` 属性,在 PPT 中转换后手动添加阴影

后续优化

这一套操作下来,熟悉大模型的伙伴可能会觉得:就这?连 MCP 和 Skills 都没用上,没啥新意。

但对于一些小白 —— 比如连 OpenCode 或终端是什么都不知道的小伙伴 —— 操作过程中还是会遇到不少问题的。

不过我觉得,AI 时代,只要能说得清楚、有明确报错信息的问题,丢给 AI 都能解决。所以还是希望能引导更多人去动手试试看。

后续计划研究一下 Skills,正好扣子 skills 不是也出了吗,打算把这些流程固化进去,尽可能实现一次生成就能满足效果 的情况。

最后

PPT 这件事,难的从来不是内容,而是怎么让内容被看见。

现在有了 OpenCode + SVG 这条路,至少「呈现」这一步,可以交给 AI 先跑一版了。

剩下的,就是你来把控方向。


有问题欢迎留言,一起交流


📌 转载信息
原作者:
Vigorxu
转载时间:
2026/1/20 18:06:18

OpenCode 竟然没有 “任务完成 / 请求权限 / 运行失败时发出通知” 这个功能。mohak34/opencode-notifier 这个插件倒是可以实现,但是最近的版本这个插件会导致 OpenCode 自带的 bun 发生 Segmentation fault,运行两步就崩溃。

于是我原汤化原食自己 vibe 了一个,把压缩包里的文件放在 ~/.config/opencode/plugins 里即可。

session-notify.zip

可以在 47 行配置是否弹出通知以及是否发出声音,一般只推荐开其中一个。Windows 可用,Linux 和 MacOS 我没测试,大概也可用,Maybe。


📌 转载信息
原作者:
MUTED64
转载时间:
2026/1/19 18:30:21

OpenCode 配置指南

配置文件位置

  • 主配置文件: ~/.config/opencode/opencode.json (供应商和模型配置)
  • 认证文件: ~/.local/share/opencode/auth.json (API Key 存储)
  • 全局提示词: ~/.config/opencode/AGENTS.md (全局行为规范)
  • 项目提示词: <项目根目录>/AGENTS.md (项目特定规范)

快速配置步骤

1. 配置供应商

~/.config/opencode/opencode.json 中添加供应商配置:

{ "$schema": "https://opencode.ai/config.json", "provider": { "供应商名称": { "options": { "baseURL": "https://api.供应商域名.com/v1" }, "models": {} } } } 

参数说明:

  • 供应商名称: 自定义供应商名称 (如 claude、openai智谱 aixxx 中转站 `)
  • baseURL: API 服务的基础 URL

2. 配置 API Key

方式一:使用命令行 (推荐)

# 登录或更新某个供应商的 Key
opencode auth login

# 查看当前已配置的所有供应商和 Key 状态
opencode auth list

# 删除指定供应商的 Key
opencode auth logout <供应商名称>

方式二:手动编辑配置文件

~/.local/share/opencode/auth.json 中添加对应供应商的 API Key:

{ "供应商名称": { "type": "api", "key": "你的API密钥" } } 

注意: auth.json 中的供应商名称必须与 opencode.json 中的供应商名称完全一致。

3. 配置模型

在供应商下添加模型配置:

{ "provider": { "供应商名称": { "options": { "baseURL": "https://api.供应商域名.com/v1" }, "models": { "claude-sonnet-4-5-20250929": { "id": "claude-sonnet-4-5-20250929", "name": "Claude Sonnet 4.5", "cost": { "input": 3, "output": 15 }, "limit": { "context": 200000, "output": 64000 }, "reasoning": true, "temperature": true, "tool_call": true, "attachment": true } } } } } 

模型参数说明:

参数类型说明
idstring模型的唯一标识符,用于 API 调用
namestring模型的显示名称
cost.inputnumber输入 token 成本 (每百万 token 美元)
cost.outputnumber输出 token 成本 (每百万 token 美元)
limit.contextnumber最大上下文长度 (token 数)
limit.outputnumber最大输出长度 (token 数)
reasoningboolean是否支持推理模式
temperatureboolean是否支持温度参数调节
tool_callboolean是否支持函数 / 工具调用
attachmentboolean是否支持文件附件

API Key 管理

优先级机制

OpenCode 选择 API Key 的优先级如下:

  1. 环境变量 (最高优先级)

    • 例如: ANTHROPIC_API_KEYOPENAI_API_KEYOPENCODE_<供应商名称>_APIKEY
    • 只要设置了环境变量,优先使用环境变量中的值
  2. 本地配置文件 (备选)

    • 如果没有设置环境变量,才使用 ~/.local/share/opencode/auth.json 中的 Key
    • 此时使用的是该供应商最后一次执行 auth login 时输入的 Key

覆盖机制

  • 对同一个供应商多次执行 opencode auth login后一次登录会覆盖前一次的 Key
  • 环境变量始终优先于配置文件

配置示例

完整配置示例

opencode.json:

{ "$schema": "https://opencode.ai/config.json", "provider": { "供应商1": { "options": { "baseURL": "https://api.供应商1域名.com/v1" }, "models": { "claude-sonnet-4-5-20250929": { "id": "claude-sonnet-4-5-20250929", "name": "Claude Sonnet 4.5", "cost": { "input": 3, "output": 15 }, "limit": { "context": 200000, "output": 64000 }, "reasoning": true, "temperature": true, "tool_call": true, "attachment": true } } }, "供应商2": { "options": { "baseURL": "https://api.供应商2域名.com/v1" }, "models": { "claude-haiku-4-5-20251001": { "id": "claude-haiku-4-5-20251001", "name": "Claude Haiku 4.5", "cost": { "input": 1, "output": 5 }, "limit": { "context": 200000, "output": 64000 }, "reasoning": true, "temperature": true, "tool_call": true, "attachment": true } } } } } 

auth.json:

{ "供应商1": { "type": "api", "key": "你的API密钥1" }, "供应商2": { "type": "api", "key": "你的API密钥2" } } 

安全建议

  1. 文件权限

    • 设置 auth.json 为仅当前用户可读: chmod 600 ~/.local/share/opencode/auth.json
    • 不要将 auth.json 提交到版本控制系统
  2. 环境隔离

    • 开发环境和生产环境使用不同的 API Key
    • 推荐使用环境变量或项目根目录下的 .env 文件管理 Key
    • 确保 .env 已加入 .gitignore
  3. Key 管理

    • 定期轮换 API Key
    • 使用具有适当权限限制的 API Key
    • auth.json 目前为明文存储,请勿随意分享
  4. 备份

    • 建议定期备份配置文件
    • auth.json 备份时注意安全存储

提示词配置

OpenCode 支持通过 AGENTS.md 文件自定义 AI 助手的行为规范和工作流程。

提示词类型

OpenCode 提供两种级别的提示词配置:

  1. 全局提示词 - 对所有项目生效
  2. 项目提示词 - 仅对当前项目生效

提示词文件位置

类型文件路径作用范围
全局提示词~/.config/opencode/AGENTS.md所有项目
项目提示词<项目根目录>/AGENTS.md当前项目

优先级机制

当同时存在全局和项目提示词时:

  • 项目提示词优先级更高,会覆盖全局提示词中的相同配置
  • 两者会合并使用,项目提示词补充项目特定的规范

提示词内容示例

全局提示词 (~/.config/opencode/AGENTS.md):

# OpenCode 全局提示词 ## 核心原则 - 严谨工作态度,保证完美质量标准
- 直接输出代码或方案,禁止客套话
- 回复简洁明了,使用中文

## 代码规范 - 遵循项目现有代码风格
- 优先编写直观的线性逻辑
- 考虑边界条件和极端情况

## Git 工作流 - 仅在明确要求时提交
- 遵循约定式提交消息格式
- 提交前检查 git status 和 git diff

项目提示词 (<项目根目录>/AGENTS.md):

# 项目名称 - 项目特定提示词 ## 项目概述
这是一个 XXX 项目,使用 XXX 技术栈。

## 目录结构 

project/
├── src/ # 源代码
├── tests/ # 测试文件
└── docs/ # 文档

 ## 项目规范 - 使用 TypeScript 严格模式
- 测试覆盖率要求 80% 以上
- 所有 API 必须有 JSDoc 注释

## 工作流程 1. 修改代码前先运行测试
2. 完成后运行 `npm run lint``npm test` 3. 提交信息格式: `<type>(<scope>): <subject>` 

提示词配置建议

  1. 全局提示词

    • 定义通用的代码规范和工作原则
    • 设置统一的命名规范和注释风格
    • 配置 Git 提交规范
  2. 项目提示词

    • 说明项目的技术栈和架构
    • 描述项目特定的目录结构
    • 定义项目特有的工作流程和规范
    • 列出项目常用的命令和工具
  3. 最佳实践

    • 保持提示词简洁明确,避免冗长描述
    • 使用 Markdown 格式,便于阅读和维护
    • 定期更新提示词,与项目演进保持同步
    • 将敏感信息(如密码、密钥)排除在提示词之外

如何创建提示词文件

创建全局提示词:

# 创建配置目录(如果不存在) mkdir -p ~/.config/opencode

# 创建全局提示词文件 touch ~/.config/opencode/AGENTS.md

# 编辑文件
vim ~/.config/opencode/AGENTS.md

创建项目提示词:

# 在项目根目录创建 cd /path/to/项目目录
touch AGENTS.md

# 编辑文件
vim AGENTS.md

# 建议将 AGENTS.md 加入版本控制
git add AGENTS.md
git commit -m "docs: add project-specific agent instructions" 

提示词生效时机

  • 全局提示词: OpenCode 启动时自动加载
  • 项目提示词: 打开项目目录时自动加载
  • 修改后: 需要重启 OpenCode 或重新打开项目才能生效

常见问题

如何添加新的 API 供应商?

  1. opencode.json 中添加供应商配置 (baseURL 和 models)
  2. auth.json 中添加对应的 API Key 或使用 opencode auth login
  3. 重启 OpenCode

如何切换不同的模型?

在 OpenCode 界面中按 Ctrl+P,选择 “Switch Model” 即可切换已配置的模型。

如何验证配置是否正确?

启动 OpenCode 后,尝试发送一条消息。如果配置正确,模型会正常响应。如果出错,检查:

  • 供应商名称是否在两个文件中一致
  • API Key 是否正确
  • baseURL 是否可访问

支持哪些 API 供应商?

OpenCode 支持所有兼容 OpenAI API 格式的供应商,包括:

  • Anthropic Claude
  • OpenAI GPT
  • Azure OpenAI
  • 智谱 AI
  • 各类第三方 API 代理服务

环境变量和配置文件同时存在时使用哪个?

环境变量优先级更高。如果设置了环境变量 (如 ANTHROPIC_API_KEY),OpenCode 会优先使用环境变量中的 Key,忽略 auth.json 中的配置。

更多帮助


📌 转载信息
转载时间:
2026/1/19 18:27:32