From a841417e0d8c901dab428477df38c408f18627b5 Mon Sep 17 00:00:00 2001 From: Tiago Date: Tue, 22 Sep 2026 12:03:48 -0300 Subject: [PATCH] =?UTF-8?q?fix(v2):=20=E4=BF=AE=E5=A4=8D=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=AB=AF=E5=85=A5=E5=8F=A3=20schema=20=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E4=B8=8E=20auth.json=20=E5=87=AD=E6=8D=AE=E5=A4=8D=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/server.ts: 默认导出补充空 setup,避免 V2 服务端因缺少 setup/effect 而通过不了 PluginModule 校验,导致 cli.json 中的插件未激活、面板不显示 - src/v2/commands.ts: readOpenAIOAuthToken 泛化为 readAuthCredential, 同时支持 oauth(access) 与 api(key);V2 的 Provider.Info 不含凭据, provider list 拿不到 key 时回退读取 auth.json,修复 DeepSeek 等 提供商在 V2 下显示「API Key not set」的问题 --- src/server.ts | 11 ++++++++++- src/v2/commands.ts | 37 ++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/server.ts b/src/server.ts index 6228c7f..e14fb17 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,9 +2,18 @@ import type { Plugin, PluginModule } from "@opencode-ai/plugin" const server: Plugin = async () => ({}) -const mod: PluginModule = { +/** + * V2 (opencode2) requires the default export to expose an `id` plus a + * `setup` (or `effect`) function. This plugin has no server-side behavior, + * so an empty setup keeps the module schema-valid and active on V2 while the + * `server` field stays for V1 detection. + */ +const setup = async () => {} + +const mod: PluginModule & { setup: () => Promise } = { id: "opencode-visual-cache", server, + setup, } export default mod diff --git a/src/v2/commands.ts b/src/v2/commands.ts index 9f58c74..6b2be54 100644 --- a/src/v2/commands.ts +++ b/src/v2/commands.ts @@ -21,9 +21,13 @@ function extractToolParts(msg: Record): Array> return msg.content.filter((p: Record) => p?.type === "tool") } -/** 读取 OpenAI 的 OAuth access token(存放于 auth.json,与 provider.key 分开)。 +/** 读取 OpenCode auth.json 中某个 provider 的凭据: + * - `oauth` 类型 → `access` token(如 OpenAI 订阅登录) + * - `api` 类型 → `key`(如 DeepSeek 等 API key 提供商) + * V2 的 provider list 不暴露凭据(Provider.Info 无 key 字段), + * 这里作为自动复用 OpenCode 已认证凭据的兜底。 * V2 无 state 路径 API,依次尝试 XDG data 目录、~/.local/share;全部失败返回空串。 */ -function readOpenAIOAuthToken(): string { +function readAuthCredential(providerID: string): string { try { const loader = typeof process !== "undefined" ? process?.getBuiltinModule : undefined const fs = loader?.("node:fs") as { readFileSync(path: string, encoding: "utf8"): string } | undefined @@ -38,10 +42,11 @@ function readOpenAIOAuthToken(): string { if (!path) continue try { const auth = JSON.parse(fs.readFileSync(path, "utf8")) as Record - const openai = auth.openai - if (openai && typeof openai === "object") { - const record = openai as Record + const entry = auth[providerID] + if (entry && typeof entry === "object") { + const record = entry as Record if (record.type === "oauth" && typeof record.access === "string") return record.access + if (record.type === "api" && typeof record.key === "string") return record.key } } catch { /* try the next known auth path */ } } @@ -51,8 +56,9 @@ function readOpenAIOAuthToken(): string { } } -/** V2 版 findOpencodeKey:从 V2 provider list 找 OpenCode 已认证的 key(对齐 V1 api.state.provider)。 - * OpenAI 优先读取 auth.json 的 OAuth token;其他 provider 读取 provider.key / provider.options.apiKey。 +/** V2 版 findOpencodeKey:优先使用 V2 provider list 暴露的 key(宿主提供时), + * 否则回退读取 auth.json 的凭据——V2 的 Provider.Info 不含 key 字段, + * provider list 拿不到 key 时以 auth.json 为准(对齐 V1 api.state.provider 的效果)。 * 导出供 index.tsx 的余额轮询复用。 */ export function findOpencodeKeyV2(context: Context, provider: BalanceProvider): string { try { @@ -60,17 +66,14 @@ export function findOpencodeKeyV2(context: Context, provider: BalanceProvider): const id = provider.id.toLowerCase() const hit = provs.find((p) => String(p.id ?? "").toLowerCase() === id) ?? provs.find((p) => String(p.id ?? "").toLowerCase().startsWith(id)) - if (id === "openai") { - const oauth = readOpenAIOAuthToken() - if (oauth) return oauth + if (hit) { + const k = typeof hit.key === "string" ? hit.key : "" + if (k) return k + const optionKey = typeof hit.options?.apiKey === "string" ? hit.options.apiKey : "" + if (optionKey) return optionKey } - if (!hit) return "" - const k = typeof hit.key === "string" ? hit.key : "" - if (k) return k - return typeof hit.options?.apiKey === "string" ? hit.options.apiKey : "" - } catch { - return "" - } + } catch { /* fall through to auth.json */ } + return readAuthCredential(provider.id) } /** 当前路由 sessionID(V2 ui.router.current();Route = { type: "session", sessionID })。 */