更懂你，更安全！枫清科技（Fabarta）首发中国版OpenClaw“龙虾”, 重新定义本地AI生产力
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 的时候都是怎么分析开源项目的源代码
最近一直在玩 vibe coding ，主力工具是 OpenCode 和 Gemini CLI，用着其实挺顺手，基本需求都能 cover 。 但用久了，发现这哥俩也是各有特点： OpenCode 是我的“远程工作站”。最大的爽点就是部署灵活，很方便远程访问，而且能自由“换脑”——我现在主力搭配的是 MiniMax 2.1 / Code Plan - Plus ，响应快得飞起，额度还根本用不完……所以一些轻量级或者需要快速验证想法的项目，我都会丢给它。其他模型还没怎么试，感觉这个组合已经够香了 Gemini CLI 则是我的“精准武器”。特别是处理 Rust 项目的时候，Gemini 3 Pro 的表现明显比 MiniMax 更稳、更准，那种“懂我”的感觉很到位。但痛点也很明显：额度太不经用了，虽然用用给我切到 Flash 模型，被打断的感觉很不尽兴.. 明明就快出来了... 所以，问题来了。我看 Antigravity 的使用要突破层层障碍，它有什么独特的绝活让你无法割舍的?
在线地址： https://johnsmith2078.github.io/webjrpg/完成度比我想象地高很多，第一次直出的版本就完全能跑了，不过后续稍微迭代了一下，加了一些传统 RPG 的特性，omo 也确实是 token 消耗器，几下把我 codex 的周额度就花完了，目前流程还很短，只有十来分钟的样子，不过毕竟只是一个实验性的项目。
Antigravity 账户接入 Opencode支持(包含详细配置和Oh My OpenCode 配置支持)
最近对 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.mdOpenCode 集成配置在用户目录下的 .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" } } } 欢迎讨论和提问，谢谢各位佬。📌 转载信息来源： https://linux.do/t/topic/1513743转载时间： 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.mdOpenCode 集成配置在用户目录下的 .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" } } } 欢迎讨论和提问，谢谢各位佬。📌 转载信息来源： https://linux.do/t/topic/1513743转载时间： 2026/1/25 23:15:37
我和同事们目前正在利用 OpenCode（Claude Code 的一个替代方案）进行一项小研究。目前我们刚开始测试 gpt-5-mini，看看它在编程辅助方面的性能能否与 Claude Sonnet 媲美。也非常欢迎大家亲自尝试研究并分享心得，比如哪种 LLM 模型成功率最高，以及使用了什么样的 Prompt（提示词）。注：我是来自印尼的用户，正在使用 Google Gemini 协助翻译论坛里的讨论。如果翻译有不准确的地方，还请大家多多包涵，手下留情。 github.com GitHub - galpt/you: An autonomous AI agent orchestrator that...An autonomous AI agent orchestrator that coordinates an entire software company within OpenCode 📌 转载信息来源： https://linux.do/t/topic/1510629原作者： galpt转载时间： 2026/1/25 08:06:35
opencode 尽量不要更新，web 端使用当前最新版本存在严重 bug
受到影响的版本为 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📌 转载信息来源： https://linux.do/t/topic/1505630原作者： ufhy转载时间： 2026/1/23 19:24:35
花了两三天时间，查了大量资料，终于把 oh-my-opencode 配置明白了。注意： 以下所有配置均已进行脱敏处理（隐藏了具体的 session_id、本机用户路径、工作区路径以及真实的 provider 地址）。无广 我自用的 provider 未完全脱敏，感觉脱敏看起来好奇怪，model key 我懒得处理。 个人不推荐任何 providerAgents 与 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.jsonWindows: %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.jsonWindows: %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.jsonapi key 的配置和 oauth 配置在~/.local/share/opencode /auth.json%USERPROFILE%\.local\share\opencode\auth.json{ "x": { "type": "api", "key": "<API_KEY>" } } 📌 转载信息来源： https://linux.do/t/topic/1504587原作者： favorPatato转载时间： 2026/1/23 15:36:07
前几天看到: 让 GPT5.2 在 OpenCode 里说人话思路，我进行了尝试，他使用了 gemini-3-flash 对 GPT5.2 的思考块进行翻译，我用过之后，经常翻译超时，于是想到，能不能让 GPT5.2 自己翻译自己，基于这位佬友的代码进行了二开，效果嘎嘎好。效果如图：安装：1、复制本帖最下方代码，保存为 think-translator.ts（文件名随意）2、打开 think-translator.ts3、把 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); }, }; }; 📌 转载信息来源： https://linux.do/t/topic/1504823原作者： Tongz转载时间： 2026/1/23 15:34:53