# justwokermore **Repository Path**: opencode-plugin/justwokermore ## Basic Information - **Project Name**: justwokermore - **Description**: justwokermore -支持多key轮询 - **Primary Language**: TypeScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-19 - **Last Updated**: 2026-10-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # JustWoker More OpenCode Provider Plugin **提供商 ID**: `justwokermore` | **显示名称**: JustWoker More | **Base URL**: `https://api.justwoker.icu/v1` OpenCode 插件,集成 JustWoker API(api.justwoker.icu)网关,支持 API Key 多密钥轮询以缓解速率限制。 ## 功能 - **多 Key 轮询** — 输入多个 API Key(逗号分隔),每请求自动轮换,减缓 `429` 速率限制 - **错误容错** — 单个 Key 连续错误 3 次自动跳过,全部不可用时重置计数 - **模型支持** — 内置 `claude-opus-4-8`(Anthropic Opus 旗舰,1M 上下文,128K 输出,支持思考/工具调用/视觉) - **原生 Anthropic 协议** — 直连 `/v1/messages`,思考块、流式、工具调用均原生支持 - **思考内容分离** — 自动注入 `thinking: { type: 'adaptive', display: 'summarized' }`,返回可读思考摘要 - **短暂错误重试** — 上游 5xx 与 Cloudflare 边缘偶发空 403 统一转为 429,交由 OpenCode 重试 - **未授权兜底** — 未配置 Key 时直接返回 401 并提示授权,不触发无意义重试 - **调试日志** — `~/.justwokermore/debug.log`,由环境变量控制开关 - **凭证存储** — API Key 持久化到 `~/.justwokermore/auth.json` ## 安装 ### 前提 - Node.js 22+ - OpenCode ### 步骤 ```bash # 1. 进入项目 cd justwokermore # 2. 安装依赖 npm install # 3. 构建 npm run build ``` ## 配置 ### 1. 配置 opencode.json ```json // opencode.json { "plugins": [ "/path/to/justwokermore" ] } ``` ### 2. 添加凭据 在 OpenCode 中执行 `/connect` 命令,选择 **JustWoker More**: ``` ┌ 创建 API Key:https://api.justwoker.icu/keys │ │ 邀请链接(注册可额外获得额度): │ https://api.justwoker.icu/sign-up?aff=Mglv │ │ 输入 JustWoker More API Keys(多key用逗号分隔) │ ──────────────────────────────── │ sk-key1,sk-key2,sk-key3 └ enter ``` > 多个 Key 用逗号分隔,插件会自动轮询使用。未授权时模型请求会返回 > `未授权:请通过 /connect 命令点击 JustWoker More 进行授权`。 ### 3. 选择模型 执行 `/models` 命令,选择可用模型: | Model ID | 上游模型 | 描述 | 上下文 | 最大输出 | 输入 | 价格(输入/输出,$/1M) | |----------|----------|------|--------|----------|------|--------------------------| | `claude-opus-4-8` | `claude-opus-4-8` | Claude Opus 4.8(Anthropic Opus 旗舰) | 1M | 128K | text, image | 0.5 / 2.5 | > 价格为该网关实际计价(输入 $0.5/1M、输出 $2.5/1M)。 > 本网关模型别名与真实 id 一致(均为 `claude-opus-4-8`),无需别名改写。 ## 思考内容处理 上游原生返回 Anthropic `thinking` 内容块。claude-opus-4-8 属 Opus 4.7+, 其 `enabled` 模式仅显示 Thought 标志、思考块为空,因此插件在 `/messages` 请求体 注入 `thinking: { type: 'adaptive', display: 'summarized' }`,返回可读的思考摘要。 - 仅当请求体**未显式指定** `thinking` 时注入,避免覆盖 OpenCode 的 effort 变体选择 - 思考强度(`effort`)由 OpenCode 按用户选择经 `output_config.effort` 自动传入,插件不写死 ## 多 Key 轮询机制 ``` 请求 → requestFetch → KeyManager.getNextKey() ↓ ┌──────────────┐ │ key1 ← 首次 │ │ key2 ← 下次 │ │ key3 ← 再下次 │ └──────────────┘ ↓ 写入 x-api-key → 发起 API 请求 ``` - 循环轮询:`key1 → key2 → key3 → key1 → ...` - 错误追踪:每个 Key 连续失败 3 次后跳过 - 自动恢复:所有 Key 都被跳过时重置计数 - 成功自愈:请求成功后清零该 Key 的错误计数 ## 上游接口(实测记录) 网关 `/v1/models` 声明 `supported_endpoint_types: ["anthropic", "openai"]`,实测: | API | 路径 | 认证头 | 实测结果 | |-----|------|--------|----------| | Messages(Anthropic 原生) | `/v1/messages` | `x-api-key` + `anthropic-version` | ✅ 200,SSE 流式 / 工具 / 视觉 / 思考块均正常 | | Chat Completions(OpenAI 兼容) | `/v1/chat/completions` | `Authorization: Bearer` | ❌ 403(被 Cloudflare WAF 拦截,返回 HTML),不可用 | **结论**:插件 provider 级使用 `@ai-sdk/anthropic`,全部请求走 `/v1/messages`。 > 🚨 由此派生的两个关键差异: > 1. `@ai-sdk/anthropic` **不支持** `transformRequestBody`(该选项仅 openai-compatible 有), > 请求体由 SDK 构建,插件只能通过自定义 fetch 修正请求头与请求体(如 thinking 注入)。 > 2. Anthropic 错误体 schema 为 `{ type:'error', error:{ type, message } }`, > 而上游错误体为 `{ error:{ code, message, type } }`(无顶层 `type`)。 > 插件统一改写错误体,否则 SDK 无法解析 message,错误提示会退化为 statusText。 其他实测要点: - **流式**:标准 SSE,`thinking` 与 `text` 内容块分离 - **工具调用**:支持,返回 `tool_use` 块 - **视觉输入**:支持 base64 image 内容块 - **偶发空 403**:约 1/4 请求会被 Cloudflare 边缘拦截(空 body、`application/octet-stream`、带 `cf-ray`), 属短暂性错误,插件转 429 交 OpenCode 重试可恢复 - **首字节偏慢**:重思考请求 TTFB 实测数秒至数十秒(思考量越大越慢),故未设激进的首块超时 - **上游偶发缓冲**:网关有时会将整段响应缓冲后一次性下发(非流式观感),此为上游行为,插件仅透传 ## 错误处理 自定义 `fetch` 统一将上游错误转换为 OpenCode 可解析的 Anthropic 标准 JSON 错误流: | 场景 | 行为 | |------|------| | 401 | 上报 key;原状态码 401;`isRetryable=false` 立即报错,保留上游原始消息 | | 403 + 结构化 JSON | 上报 key;原状态码 403(真实鉴权/权限拒绝,不重试) | | 403 + 空 body | 转 429(Cloudflare 边缘偶发拦截,短暂性错误,触发重试) | | 429 | 上报 key;原状态码 429 + 透传 `Retry-After`,由 OpenCode 判断重试 | | 5xx | 转固定 429 错误流(上游暂时不可用,触发重试) | | 400 | 保留原状态码立即报错(参数校验错误,重试无意义) | | 408 / 409 | 返回原状态码,OpenCode 视为可重试 | | 网络异常 | 返回 502 标准错误流,不向 UI 抛异常 | | 未授权(无 Key) | 返回 401 并提示 `/connect` 授权,不发真实请求 | | SSE 首块 15s 超时 | 返回 429 触发 OpenCode 重试 | ## 调试日志 通过环境变量开启调试日志: ```bash # 方式一:全局 DEBUG DEBUG=1 opencode # 方式二:仅 JustWoker More OPENCODE_JUSTWOKERMORE_DEBUG=1 opencode ``` 日志位置:`%USERPROFILE%\.justwokermore\debug.log` > 环境变量在**插件加载时**读取,需在启动 OpenCode 前设置。 ### 日志格式 可读 TEXT 单行格式(对齐 agnesmore/sensemore/atriamore 约定),按 append 追加: ``` [2026-09-19T08:25:05.573Z] [INFO ] Auth loader initialized {"keyCount":3,"keys":["1:sk-9YXxe...Zzuu"],"baseURL":"https://api.justwoker.icu/v1"} [2026-09-19T08:25:05.595Z] [INFO ] Request start {"seq":1,"key":"sk-9YXxe...Zzuu","keyPos":"1/3","model":"claude-opus-4-8","endpoint":"/v1/messages"} [2026-09-19T08:25:09.864Z] [INFO ] Request success {"seq":1,"key":"sk-9YXxe...Zzuu","keyPos":"1/3","status":200,"stream":true,"ms":4243} ``` ### 确认 Key 轮询情况 **每个请求(无论成功或失败)都会记录所用 key**,用于确认轮询是否生效: | 字段 | 含义 | |------|------| | `seq` | 请求序号(插件生命周期内自增) | | `key` | 该请求实际使用的 API Key(掩码,保留前 8 位 + 末 4 位) | | `keyPos` | 该 key 在池中的位置,如 `3/8` | | `model` | 请求的模型 | | `endpoint` | 请求路径,如 `/v1/messages` | | `status` / `stream` / `ms` | 响应状态码 / 是否流式 / 耗时毫秒 | 观察 `seq` 与 `keyPos` 的对应关系即可确认轮询: ``` seq:1 keyPos:1/3 seq:2 keyPos:2/3 seq:3 keyPos:3/3 seq:4 keyPos:1/3 ← 用完一轮回到第 1 把 ``` 相关事件: | 事件 | 级别 | 说明 | |------|------|------| | `Provider config updated` | INFO | config 钩子注册 provider 与模型 | | `Auth loader initialized` | INFO | 载入 key 池,列出全部掩码 key 及序号 | | `Request start` | INFO | 请求发出前(含 key / keyPos / model) | | `Request success` | INFO | 请求成功(含状态码 / 是否流式 / 耗时) | | `Upstream error response` | WARN | 上游返回错误(含状态码 / 错误码 / 原始消息 / 是否结构化) | | `Empty 403 (Cloudflare edge block) treated as transient` | WARN | 空 403 归类为短暂性错误转 429 | | `Key error count increased` | WARN | key 连续失败计数(≥3 次将被跳过) | | `Key recovered, error count reset` | INFO | 成功请求后清零该 key 错误计数 | | `Unauthenticated request blocked` | WARN | 未授权兜底拦截 | | `First chunk timeout` | WARN | SSE 首块 15s 超时,转 429 | | `Fetch network error` | ERROR | 网络异常 | | `Dispose hook called` | INFO | 插件卸载,全局状态清空 | ## 项目结构 ``` justwokermore/ ├── package.json ├── tsconfig.json ├── src/ │ ├── index.ts # 主入口:auth hook + config hook + 共享 fetch(轮换/错误映射/thinking/首块超时) │ ├── constants.ts # 提供商配置、模型定义 │ ├── plugin/ │ │ ├── auth.ts # 凭证读写 ~/.justwokermore/auth.json │ │ └── key-manager.ts # 多 Key 轮询算法 │ └── utils/ │ └── debug-logger.ts # 文件日志(无 console.error)+ maskKey ├── scripts/ # 验证脚本 └── dist/ # 构建产物 ``` ## 验证脚本 | 脚本 | 用途 | 是否需要 Key | |------|------|--------------| | `scripts/check-error-mapping.mjs` | 错误映射离线验证(mock Response):401/403 结构化/403 空 body/429/5xx/400/408/409/网络异常/未授权/SSE | 否 | | `scripts/check-key-rotation.mjs` | **多 key 轮询 + 日志归因验证**(不写凭证文件) | 否(可用假 key) | | `scripts/check-e2e.mjs` | 端到端(需真实 Key):非流式 / 流式 / 思考块 / 工具 / 视觉 / 鉴权错误 | 是 | ```bash # 错误映射(离线,无需 Key) node scripts/check-error-mapping.mjs # 多 key 轮询(用假 key 即可,验证轮询顺序与日志归因) node scripts/check-key-rotation.mjs keyA,keyB,keyC # 端到端(需真实 Key) node scripts/check-e2e.mjs sk-xxxxxxxx ``` ## 开发 ```bash npm run typecheck # 类型检查 npm run build # 构建到 dist/ ``` ## Key 黑名单(blacklist.json) KeyManager 会为每个 Key 维护错误计数,持久性错误(`401/403/400` 等)触发 `reportError`(计数 +1), 当 `errorCount >= 3` 时该 Key 被临时拉黑,轮询时自动跳过;请求成功一次即触发 `reportSuccess` 复位计数。 黑名单状态通过快照文件跨重启保留,重启后自动恢复各 Key 的错误计数。 快照文件格式(与插件写盘格式一致): ```json { "updatedAt": 1759800000000, "keys": [ { "key": "sk-xxx1", "errorCount": 3, "blocked": true }, { "key": "sk-xxx2", "errorCount": 0, "blocked": false } ] } ``` 目标路径:`%USERPROFILE%\.justwokermore\blacklist.json` 复制方法(可选):将项目 `jsonc/blacklist.jsonc` 复制到 `%USERPROFILE%\.justwokermore\` 并重命名为 `blacklist.json`。不复制也不影响运行——插件仅在内存中计数,不跨重启保留;快照读写失败仅记录 debug 警告,绝不中断主流程。 ## 配置文件 插件运行所需的配置文件位于本仓库 ./jsonc/ 目录下(provider.jsonc 与 models.jsonc)。使用前将这两个文件复制到用户目录 %USERPROFILE%\.justwokermore/ 下(缺失时插件启动会 fail fast 报错)。复制后可按需编辑模型列表与提供商信息(支持注释),修改后重启插件生效。