# teamomore **Repository Path**: opencode-plugin/teamomore ## Basic Information - **Project Name**: teamomore - **Description**: teamomore - -支持多key轮询 (teamorouter, 没有免费模型,老板玩的是文字游戏:free = 福利 = 按 0.01折计费 且 平台最低充值 $5 美元起步=¥34.5) - **Primary Language**: TypeScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-22 - **Last Updated**: 2026-09-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Teamo More OpenCode Provider Plugin **提供商 ID**: `teamomore` | **显示名称**: Teamo More | **Base URL**: `https://api.teamorouter.cn/v1` OpenCode 插件,集成 TeamoRouter(api.teamorouter.cn)网关,支持 API Key 多密钥轮询以缓解速率限制。 ## 功能 - **多 Key 轮询** — 输入多个 API Key(逗号分隔),自动循环切换,减缓 `429` 速率限制 - **错误容错** — 单个 Key 连续错误 3 次自动跳过,全部不可用时重置计数 - **模型支持** — 内置 3 个 chat 模型(见下表),免费模型按额度调用(200 次/模型/天) - **思考内容分离** — DeepSeek 系模型声明 `interleaved` 后 OpenCode 渲染独立思考区;插件注入 `thinking` 开关兜底 - **模型别名映射** — OpenCode 上送小写别名(如 `deepseek-v4.1-flash`),插件改写为上游真实 id(`deepseek-flash-free`)后请求 - **短暂错误重试** — 上游 5xx 与 `422` 统一转为 429,交由 OpenCode 重试 - **未授权兜底** — 未配置 Key 时直接返回 401 并提示授权,不触发无意义重试 - **首 chunk 超时保护** — SSE 流 15 秒无首块时返回 429,交由 OpenCode 重试(防上游挂起) - **调试日志** — `~/.teamomore/debug.log`,由环境变量控制开关 - **凭证存储** — API Key 持久化到 `~/.teamomore/auth.json` ## 模型列表 | Model ID(OpenCode 上送) | 上游模型 id | 描述 | 上下文 | 最大输出 | 输入 | 额度 | |--------------------------|-------------|------|--------|----------|------|------| | `deepseek-v4.1-flash` | `deepseek-flash-free` | DeepSeek V4.1 Flash 福利版 | 1M | 384K | text+image | 200 次/天 | | `deepseek-v4-flash` | `deepseek-v4-flash-free` | DeepSeek V4 Flash 福利版 | 1M | 384K | text | 200 次/天 | | `glm-5.3-flash` | `glm-5.3-flash-free` | GLM 5.3 Flash 福利版 | 1M | 128K | text+image+video | 200 次/天 | > **模型别名映射**:OpenCode 上送的是表中 `Model ID` 列(小写别名,已去掉 `-free` 后缀), > 插件通过它匹配 `constants.ts` 中的模型定义,再用「上游模型 id」列请求上游。 > ⚠️ **老板玩上了文字游戏**:实际测试 `free` 模型并非免费模型,而是**福利模型**—— > 按 **0.01 折**优惠计费,且平台**最低充值 $5 美元起步(¥34.5)**。 > **关于 `jev`**:jev 为 TeamoRouter 的 `/v1/systemone` 分类接口(请求体为 > `state` + `questions` 结构),与 OpenAI chat completions 协议不兼容, > 不适合作为 OpenCode 聊天模型,故本插件不注册。 ## 安装 ### 前提 - Node.js 22+ - OpenCode ### 步骤 ```bash # 1. 进入项目 cd teamomore # 2. 安装依赖 npm install # 3. 构建 npm run build ``` ## 配置 ### 1. 配置 opencode.json ```json // opencode.json { "plugins": [ "/path/to/teamomore" ] } ``` ### 2. 添加凭据 在 OpenCode 中执行 `/connect` 命令,选择 **Teamo More**: ``` ┌ Jey 模型无限次免费调用,其它 free 模型 200 次/模型/天 │ │ 创建 apiKey:https://teamorouter.cn/dashboard?tab=api-keys │ │ 使用邀请链接注册:https://teamorouter.cn/?i=3ee4abcba7 │ │ 输入 API Keys(多key用逗号分隔) │ ──────────────────────────────── │ sk-teamo-key1,sk-teamo-key2,sk-teamo-key3 └ enter ``` > 多个 Key 用逗号分隔,插件会自动轮询使用。未授权时模型请求会返回 > `未授权:请通过 /connect 命令点击 Teamo More 进行授权`。 ### 3. 选择模型 执行 `/models` 命令,选择可用模型(见上方「模型列表」)。 ## 思考内容处理 DeepSeek 系模型(`deepseek-v4.1-flash` / `deepseek-v4-flash`)声明 `interleaved: { field: 'reasoning_content' }`,OpenCode 将上游返回的 `reasoning_content` 渲染为独立思考区。 插件额外在请求体注入 `thinking: { type: "enabled" }` 作为开关兜底 (防止上游偶发把推理混入 `content`)。`reasoning_effort` 由 OpenCode 按用户选择的 档位自动传入(SDK 在 `transformRequestBody` 之前写入),插件仅透传、不写死。 携带 `tools` 的请求中,历史 assistant 消息缺失/空 `reasoning_content` 时 自动补非空占位符 `…`,避免 DeepSeek 网关 400。 ## 多 Key 轮询机制 ``` 请求 → chat.headers → KeyManager.getNextKey() ↓ ┌──────────────┐ │ key1 ← 首次 │ │ key2 ← 下次 │ │ key3 ← 再下次 │ └──────────────┘ ↓ 写入 Authorization → 发起 API 请求 ``` - 循环轮询:`key1 → key2 → key3 → key1 → ...` - 错误追踪:每个 Key 连续失败 3 次后跳过 - 自动恢复:所有 Key 都被跳过时重置计数 - 成功自愈:请求成功后清零该 Key 的错误计数 ## 错误处理 自定义 `fetch` 统一将上游错误转换为 OpenCode 可解析的标准 JSON 错误流: | 场景 | 行为 | |------|------| | 401 / 403 | 原样返回上游 Response;`isRetryable=false` 立即报错(不重试),保留上游原始错误消息 | | 429 | 原样返回上游 Response + `Retry-After`;由 OpenCode 判断重试或 fallback | | 5xx / 422 | 返回固定 429 错误流,触发 OpenCode 重试(上游推理偶发失败) | | 400(校验错误 / `insufficient_balance` 钱包余额不足) | 保留原状态码立即报错(参数/账号级持久错误,重试无意义) | | 408 / 409 | 返回原状态码,OpenCode 视为可重试 | | 网络异常 | 返回 502 标准错误流,不向 UI 抛异常 | | 未授权(无 Key) | 返回 401 并提示 `/connect` 授权,不发真实请求 | | 首 chunk 15s 超时 | 返回 429 触发 OpenCode 重试(防上游挂起) | ## 调试日志 通过环境变量开启调试日志: ```bash # 方式一:全局 DEBUG DEBUG=1 opencode # 方式二:仅 Teamo More OPENCODE_TEAMOMORE_DEBUG=1 opencode ``` 日志位置:`%USERPROFILE%\.teamomore\debug.log` > 环境变量在**插件加载时**读取,需在启动 OpenCode 前设置。 ### 日志格式 可读 TEXT 单行格式(对齐 agnesmore/atriamore 家族约定),按 append 追加: ``` [2026-09-22T08:25:05.573Z] [INFO ] Auth loader initialized {"keyCount":2,"keys":["1:sk-teamo...fce9","2:sk-teamo...d4e5"]} [2026-09-22T08:25:05.595Z] [INFO ] Request start {"seq":1,"key":"sk-teamo...fce9","keyPos":"1/2","model":"deepseek-flash-free","endpoint":"/v1/chat/completions"} [2026-09-22T08:25:09.864Z] [INFO ] Request success {"seq":1,"key":"sk-teamo...fce9","keyPos":"1/2","status":200,"stream":false,"ms":4046} ``` ### 确认 Key 轮询情况 **每个请求(无论成功或失败)都会记录所用 key**,用于确认轮询是否生效: | 字段 | 含义 | |------|------| | `seq` | 请求序号(插件生命周期内自增) | | `key` | 该请求实际使用的 API Key(掩码,保留前 8 位 + 末 4 位) | | `keyPos` | 该 key 在池中的位置,如 `3/8` | | `model` | 请求的模型(已改写为上游真实 id) | | `endpoint` | 请求路径,如 `/v1/chat/completions` | | `status` / `stream` / `ms` | 响应状态码 / 是否流式 / 耗时毫秒 | 观察 `seq` 与 `keyPos` 的对应关系即可确认轮询: ``` seq:1 keyPos:1/2 seq:2 keyPos:2/2 seq:3 keyPos:1/2 ← 轮询循环 ``` ## 上游接口(实测记录) 网关使用 **Chat Completions**(OpenAI 兼容),`/v1/chat/completions` 返回标准 JSON: - **模型 id**:free 模型带 `-free` 后缀(如 `deepseek-flash-free`),由 `teamorouter` 所有 - **错误体**:标准 OpenAI 风格 `{ error: { message, type, code } }`,含 `trace_id` - **额度不足**:`400` + `code: "enterprise_insufficient_balance"`(企业钱包余额), 免费模型同样走钱包校验,钱包为空时所有模型(含 free)均返回余额不足,充值后恢复 - **jev / systemone**:`/v1/systemone` 为分类接口(`state` + `questions`),非 chat 协议,插件不注册 ## 项目结构 ``` teamomore/ ├── package.json ├── tsconfig.json ├── src/ │ ├── index.ts # 主入口:auth hook + config hook + chat.headers + 共享 fetch │ ├── constants.ts # 提供商配置、模型定义 │ ├── plugin/ │ │ ├── auth.ts # 凭证读写 ~/.teamomore/auth.json │ │ └── key-manager.ts # 多 Key 轮询算法 │ └── utils/ │ └── debug-logger.ts # 文件日志(无 console.error) └── dist/ # 构建产物 ``` ## 开发 ```bash npm run typecheck # 类型检查 npm run build # 构建到 dist/ ```