这是本插件在本机的实测记录,已去除账号、密钥与本机绝对路径等敏感信息。
产物:dist/qoder2api.so(cliproxy_plugin_init ABI,CGO c-shared)。
许可:GPL-3.0(移植自 QCCG/qoder2api)。
用一份 qoder2api 桌面端导出的国际版账号配置(多账号 bundle)做导入,导入过程暴露并修掉了 4 个真实缺陷。
原先只认扁平 token/device_token 字段,导出格式是
{"format":"qoder2api-accounts","accounts":[{"secret":"{\"device_token\":…,\"refresh_token\":…}"}]}。
现在:auth.parse 识别导出文件并一个文件展开成多个 CPA 账号(AuthParseResponse.Auths),
保留每条账号的 region/name/email/plan/id;secret 既可嵌 JSON 也可为明文 token;
坏条目跳过并计数,全都不可用时报错并提示“导出时要勾选包含凭证”。
宿主把 pluginapi.HTTPResponse(无 json tag)直接放进 RPC 信封,线上键名是 Go 字段名
StatusCode/Headers/Body;插件按 status_code 解析 → 状态码永远 0 →
bridge/client.go 的 StatusCode != 200 判定把所有非流式上游请求判成失败:
模型清单、额度、签到、非流式对话全废。错误体里带着上游真实响应,看起来像“凭证被拒”,极难排查
(我此前就误判过一次)。现在两种键名都接受,且缺失状态码时直接报 ABI 不匹配而不是静默当 0。
测试 mock 也改成宿主真实形态,并加了双形态回归测试。
忠实移植了上游的 openapi.qoder.com.cn。实测国际版 device token 打国内域名一律
401 TOKEN_EXPIRE,换 openapi.qoder.sh 即 200。现在按账号 region 选域名(Endpoints.SashBase)。
另外实测国际版没有每日签到计划(/sash/api/v1/me/daily-check-in* → 404 NotFound),
只有 VIEW_DETAILS 促销活动;插件不会误领促销,并明确提示“该区域无每日签到活动”。
宿主判断“可否刷新”看 auth.Metadata["refresh_token"],并靠 NextRefreshAfter 排期。
插件原先两者都没给,导致 /auth-files/refresh 返回 results: [](根本不尝试)。
现在 OAuth 账号会带上 refresh_token 元数据与下次刷新时间;PAT 账号不会伪造 refresh token。
用户提示 Qwen3.8-Flash 是免费调用、可直接测。实测确实能出字,但上游有排队制:
繁忙时返回 HTTP 403,正文是
{"isQueued":true,"serviceAvailable":false,"queueType":"p3","retryAfterSeconds":30}
(retryAfterSeconds 在 9~30 秒之间浮动)。
修复前:这个 403 被原样抛出,宿主标成 insufficient_quota / permission_error,把用户
引向充值(其实免费模型与额度无关)。
修复后:
internal/bridge/queue.go识别排队信号(多层转义 JSON +data:前缀 + 正则兜底都能认), 该错误被明确标注为upstream_model_busy;- 按上游建议时长等待后重开上游(默认最多 2 次),期间调用方无感;
等待预算耗尽才返回 503 +
qoder_model_busy,消息写明“不是额度或凭证问题”; - 新增配置
queue_max_waits(默认 2,0= 快速失败)与queue_wait_seconds(默认 0 = 跟随上游建议值),支持PATCH /v0/management/plugins/qoder2api/config热重载; - fast-fail 模式(
queue_max_waits: 0)不会退化成 1s/2s 的瞬时重试去白打上游。
真实客户端实测发现流式看似正常、内容全空:宿主写出的是 data: data: {...}。
根因是分帧契约按协议不同:
- chat-completions:宿主处理器自己
fmt.Fprintf("data: %s\n\n", chunk)→ 插件必须发裸 JSON, 且不能发[DONE](宿主自己补); - claude / codex:宿主处理器原样写出 → 插件必须发完整 SSE 帧(
event:/data:)。
插件原先三种协议统一加 data: 前缀 → OpenAI 客户端收到无效 JSON,流式内容全空。
现在 streamWriter 按宿主声明的输出格式(rpc.format)归一化分帧;
端到端测试升级为解析每个分片(json.Valid),不再用子串匹配(旧断言正是被
data: data: 蒙过去的原因)。
插件报错后宿主会冷却该凭证(transient-error-cooldown-seconds,默认约 60 秒)。
排队失败后立刻重试同一模型会收到
auth_unavailable: no auth available ... last upstream error: 上游模型 qfmodel 排队中。
这不是新错误,是宿主在冷却期内不复用该凭证;等冷却过去或重启宿主即可。
.github/workflows/ci.yml:test(gofmt / go vet / go test)+ 五个平台的c-shared构建 (linux/amd64、linux/arm64、darwin/arm64 为必过;darwin/amd64 与 windows/amd64 标为 best-effort)- 打
v*tag 时自动发 Release(含SHA256SUMS.txt)。
- 打
- 首次 tag 构建暴露一个真问题:release job 在
download-artifact之后没有.git,gh release create直接失败(failed to run git: fatal: not a git repository)。 现在显式actions/checkout并传--repo "$GITHUB_REPOSITORY"。 pluginVersion改为可用-ldflags -X main.pluginVersion=<tag>注入:v0.1.0 的产物在宿主里 注册为version=v0.1.0(实测日志);注入值为空时回落默认版本,并有单测锁死。- 用 Release 产物(而不是本地构建)在本机真实宿主复测:插件加载 → 注册版本
v0.1.0→ 19 个模型 → 1 个账号 → 真实上游调用(免费模型,高峰期排队预算耗尽 → 503 且消息明确 “不是额度或凭证问题”)→ 进程稳定、无 panic。
宿主 internal/pluginstore 的安装契约(读源码 + 用宿主真实安装代码验证):
- 归档资产名必须精确等于
{id}_{version}_{goos}_{goarch}.zip(version 是 tag 去掉v); - 校验和资产名必须恰好是
checksums.txt,每行<sha256> <文件名>(*前缀会被剥掉); - zip 根目录下必须有且仅有一个动态库,名字是
{id}{ext}或{id}-v{version}{ext}; zip 内出现第二个动态库或嵌套路径会直接失败; - 安装落点
plugins/{goos}/{goarch}/{id}-v{version}{ext},宿主把-v<版本>剥掉后 ID 仍是qoder2api。
本仓库为此增加了:
scripts/packstore:用 Go 生成 zip 与checksums.txt(Windows runner 没有zip/sha256sum), 8 条单测锁死上面的命名与内容规则;- CI:build 阶段按 tag 打 zip,release 阶段统一汇总
checksums.txt(矩阵里每个平台各写一份同名文件会互相覆盖,导致部分 zip 校验和丢失); - 根目录
registry.json(schema_version 1,github-release 类型,只给repository, 版本交给宿主从 latest release 推导),并有测试校验它能通过宿主的ValidatePlugin规则。
用宿主真实代码做的离线验证(临时测试,验证后已移出宿主仓库):
SelectReleaseAssets → ParseChecksums → VerifyChecksum → InstallArchive
安装成功:/tmp/.../linux/amd64/qoder2api-v0.1.1.so(6,635,440 字节,zip 2,752,277 字节)
pluginFileInfoFromPath → id=qoder2api version=0.1.1
discoverCurrentPluginFiles → 1 个已安装插件(面板据此显示“已安装/可更新”)
真实宿主端到端实测(v0.1.1,独立实例端口 18319,未触碰生产实例):
GET /v0/management/plugin-store
sources: official + source-94348b6b9bec (raw.githubusercontent.com)
plugins: qoder2api install_type=github-release source_id=source-94348b6b9bec
POST /v0/management/plugin-store/qoder2api/install?source=source-94348b6b9bec
status=installed version=0.1.1 install_type=github-release
path=/tmp/cpa-store-it/plugins/linux/amd64/qoder2api-v0.1.1.so restart_required=False
重启后宿主日志:
plugin loaded plugin_id=qoder2api version=0.1.1 path=.../linux/amd64/qoder2api-v0.1.1.so
面板状态:installed_version=v0.1.1 update_available=False
插件状态接口:version=v0.1.1(与 release 一致)
落盘文件 sha256 == Release 裸产物 sha256(18d287a4ea4aec5074a8…)
plugins.store-sources指向本仓库registry.json;原手动装的plugins/qoder2api.so(v0.1.0) 已移到CLIProxyAPI/plugins-backup/(未删除,可回滚)。- 商店落点
plugins/linux/amd64/qoder2api-v0.1.1.so,sha256 与 Release 裸产物一致 (18d287a4ea4aec5074a8…)。 - 重启后:
plugin loaded version=0.1.1、19 个qoder-*模型、1 个账号、 真实调用 200(免费模型排队 102 秒后成功)、管理页 200、panic 计 0。 - 商店视图:
installed_version=v0.1.1、update_available=False。
store-sources 写成了顶格缩进,
YAML 解析失败(line 114: did not find expected '-' indicator)导致宿主起不来,停机约 2 分钟。
教训:改生产 YAML 后必须先过一遍解析器再重启,config.yaml 的 plugins 子键要缩进 2 空格。
用户报告:加载插件后,在面板用 OAuth 登录报 failed to generate authorization url。
根因:宿主把 GET /v0/management/<provider>-auth-url 路由到已注册 auth provider 的
StartLogin(auth_files_oauth_callback.go → pluginhost.StartLogin),而插件只实现了
auth.identifier/parse/refresh,缺 auth.login.start:
GET /v0/management/qoder-auth-url → 500 {"error":"failed to generate authorization url"}
宿主日志: failed to start plugin auth login error=unknown method: auth.login.start
对照实验(同一宿主):内置 provider 的 codex/anthropic/antigravity-auth-url 全部 200,
只有插件 provider(qoder)失败 —— 确认与插件无关的内置 OAuth 不受影响。
修复:按上游 qoder2api/account/oauth.go 的 device flow + PKCE 实现两个 ABI 方法
(auth_login.go):
start: {DeviceLoginBase}?nonce=&challenge=&challenge_method=S256&client_id=e883ade2-…
poll : {PollEndpoint}?nonce=&verifier=&challenge_method=S256
404 = 用户尚未授权(继续等待);200 = {token, refresh_token}
登录产物与导入路径同构(同一 parseQoderCredential 可读回;Metadata.refresh_token 让宿主
认可“可刷新”),所以额度、签到、刷新链路无需改动。另外:区域按 ?region= → 插件配置优先级选取;
单会话 10 分钟有效期;同会话上游轮询做 1.2 秒节流;上游 5xx/网络抖动只记日志并保持 pending
(不当成凭证失败)。
单测覆盖(auth_login_test.go,11 个用例):URL/PKCE 参数、state 唯一、区域选择、wait→success、
产物同构(含 parseQoderCredential 回读断言)、userinfo 失败降级、上游 5xx 保持会话、
未知/过期 state、节流、宿主字段名的 wire 契约、落盘 ID 安全性。
踩坑记录(重要):插件商店安装会把 store.version + store.release-tag 写进
config.yaml 的 plugins.configs.<id>,宿主 selectPluginFiles 按这个版本钉死文件选择:
- 手动往
plugins/<goos>/<goarch>/放更高版本的.so不会生效,且宿主静默跳过(无任何日志); - 手工改
store.version而不改release-tag同样加载不到(实测两者并存时插件根本不加载); - 临时实例不加载插件也是同一机制(config 里带着从生产复制的
store.version: 0.1.1, 放进去的qoder2api.so/qoder2api-v0.1.2.so版本都对不上)。
结论:升级只能走商店(面板更新或 POST /v0/management/plugin-store/qoder2api/install),
它会同时更新 store.version 与 release-tag 并下载 CI 产物。
# 升级:必须走商店 install(版本被 store.version + release-tag 双向钉死)
POST /v0/management/plugin-store/qoder2api/install?source=source-94348b6b9bec
→ status=installed version=0.1.2 path=plugins/linux/amd64/qoder2api-v0.1.2.so
安装后 config: store.version=0.1.2, store.release-tag=v0.1.2
落盘 sha256 == Release 裸产物 (a9e172bb022db97017083149…)
宿主日志: plugin loaded plugin_id=qoder2api version=0.1.2
# 登录入口(修复前 500 failed to generate authorization url)
GET /v0/management/qoder-auth-url
→ 200 url=https://qoder.com/device/selectAccounts?nonce=…&challenge=…&challenge_method=S256&client_id=e883ade2-…
state=11587eb2… (32 字符)
GET /v0/management/qoder-auth-url?region=cn
→ 200 url=https://qoder.com.cn/device/selectAccounts?... (区域覆盖生效)
# 轮询(接口名是 get-auth-status,不是 auth-status;插件内部真打上游)
GET /v0/management/get-auth-status?state=<state> → 200 {"status":"wait"} ×4
插件日志: oauth login started (region=global, state=11587eb2)
# 其它能力未受影响
/v1/models → 19 个 qoder-* 模型;插件状态接口 → version=v0.1.2,账号 1 个
宿主对照实验(同一实例、同一时刻):内置 codex-auth-url / anthropic-auth-url /
antigravity-auth-url 全部 200,xai-auth-url 500 且原因是 failed to start device authorization flow(上游网络不可达)——与插件无关。
用户侧复验:v0.1.2 发布后用户在其生产环境实测通过面板 OAuth 登录。
本工作实例的边界:本机实例上我只验证到 start → 200(含 PKCE 参数与区域覆盖)与
get-auth-status → wait(插件确实打了真实上游),未在本机完成一次真实浏览器授权
(我发出的链接在 10 分钟窗口内未完成授权,无新增账号落盘,属预期)。
yaml.safe_load 再重启(本轮早前曾因
store-sources 缩进顶格导致宿主起不来,停机约 2 分钟)。
需求:OAuth 登录只有国际版入口,要加国内版。
现状核实:插件 auth.login.start 本来就按 region 选端点(authLoginRegion:请求 metadata 优先,
其次插件配置),但界面上没有任何入口;宿主的 OAuth 面板是硬编码 provider 列表,插件不在其中。
所以缺的是"看得见的区域入口",不是登录能力。
实现:插件控制台页新增「添加账号(OAuth 登录)」卡片,两个按钮(国际版 / 国内版)→ 调宿主的
/v0/management/qoder-auth-url?region=global|cn 取链接,再用 /v0/management/get-auth-status?state=
轮询;成功后由宿主 savePluginLoginRecords 落盘账号。
为什么不自己实现登录路由:插件侧的 auth.login.poll 成功时会返回含 token 的 AuthData,
但它没有落盘责任 —— 自建路由去轮询会消耗掉会话却不保存账号,还要自己保证不泄漏 token。
宿主那条路已经完整(pollLogin → savePluginLoginRecords → CompleteOAuthSession)。
实测:
GET /v0/management/qoder-auth-url → 200 https://qoder.com/device/selectAccounts?...
GET /v0/management/qoder-auth-url?region=cn → 200 https://qoder.com.cn/device/selectAccounts?...
(其余 PKCE 参数一致:challenge_method=S256、client_id 相同)
验证边界(未验证部分):本机只验证到"两个区域都能生成授权链接 + 页面入口存在"。 没有国内账号,所以国内版走到"浏览器授权完成 → 账号落盘"这一段没有实测;国际版该路径此前 由用户在生产环境实测通过(走的是同一条宿主流程,只是端点域名不同)。
用户决定打开兼容开关,于是把 12 个上游 SKU 写进 extra_models(字符串形式):
extra_models: "auto,qmodel_latest,qmodel,qmodel_38max,qfmodel,kmodel_latest,kmodel,gmodel,gfmodel,dmodel,dfmodel,mmodel"重启宿主后 /v1/models 19 → 31 项(12 个模型名各有一个旧 ID 孪生项),12/12 全部到位。
端到端实测:
qoder-qfmodel → 200 内容="好" 回显=qfmodel (36s,含排队) ← 旧 ID 打通到上游
qoder-Qwen3.8-Flash → 200 内容="好" 回显=Qwen3.8-Flash (2s) ← 新 ID 不受影响
qoder-qmodel_38max → 403 upstream code=112 pricingUrl=qoder.com/pricing ← 订阅档位限制
qoder-Qwen3.8-Max → 403 同上 ← 同 SKU 的新 ID 一样被拒
最后两行是刻意对照的:Qwen3.8-Max 的 403 来自账号档位(上游 code 112 + 定价页链接),
与模型 ID 用新名还是旧名无关;免费模型 Qwen3.8-Flash 新旧 ID 都能跑通。
关闭方式:PATCH /v0/management/plugins/qoder2api/config {"extra_models": ""} + 重启宿主 → 回到 19 项。
宿主只在插件加载/重载时拉取插件模型清单,改配置不会立即刷新目录:
PATCH /v0/management/plugins/qoder2api/config {"extra_models": ""} → 200
GET /v1/models (未重启) → 仍 21 项,仍含 qoder-qfmodel / qoder-qmodel_38max
重启宿主 → 19 项,旧 ID 消失
最终生产状态(v0.1.5,extra_models 为空):19 项,全部是模型名,除 qoder-q37fmodel /
qoder-gm51model(上游实时清单里没有这两个 SKU 的名称,不编造)。
端到端实测(同一账号、同一时段):
qoder-Qwen3.8-Flash → 200 内容="好" 回显=Qwen3.8-Flash (2s)
qoder-qfmodel → 200 内容="好" 回显=qfmodel (67s,含排队)
第二条是把旧 ID 临时写进 extra_models 时跑的,证明兼容开关确实贯通到上游;随后已清空。
v0.1.4 实测时踩到:通过宿主 PATCH 传数组({"extra_models": ["qfmodel"]})会被写成 YAML 块列表
extra_models:
- qfmodel而插件自己的配置解析器只认标量行(key: value),- qfmodel 这种续行被直接跳过 →
宿主面板显示配置已保存,插件却什么都没注册,没有任何报错。反过来用字符串
{"extra_models": "qfmodel"} 立刻生效(registered_models 19 → 20,/v1/models 出现 qoder-qfmodel)。
修复:解析前归一化列表写法(块列表 / 缩进块列表 / 流式 [a, b]),并剥掉条目上的引号。
回归测试 TestDecodeConfigAcceptsYAMLListForms 覆盖 8 种写法,含"列表字段后面还有其它键"
(防续行收集吃掉后续配置)。
v0.1.3 上线后实测确认:宿主的路由表只认插件注册的 ID,所以旧 ID(qoder-qfmodel)会被宿主
直接拒掉。v0.1.4 给运维一个插件内的开关——extra_models 是显式声明,不与实时清单做同名去重:
extra_models: ["qfmodel"] # → qoder-qfmodel 重新出现在 /v1/models 并可调用去重规则由此精确化:实时清单 ↔ 兜底清单之间按上游 SKU 去重(同一模型不重复出现),
extra_models 例外(运维说了算)。回归测试:TestExtraModelsCanRestoreLegacySKUIds
与 TestBuildModelCatalogPrefixesAndDeduplicates 里的 SKU 计数断言。
用户需求:客户端/面板里"直接显示模型名称而不是 qoder 中的模型 ID"。
证据:宿主的插件模型条目不带 display_name(实测 /v1/models 只有
created/id/object/owned_by),所以可读性只能体现在模型 ID 本身:
上游实时清单(国际版, 2026-09-25):
qmodel_38max → Qwen3.8-Max qfmodel → Qwen3.8-Flash
qmodel_latest → Qwen3.7-Max qmodel → Qwen3.7-Plus
kmodel_latest → Kimi-K3 kmodel → Kimi-K2.8-Preview
gmodel → GLM-5.3 gfmodel → GLM-5.3-Flash
dmodel → DeepSeek-V4-Pro dfmodel → DeepSeek-Flash
mmodel → MiniMax-M3 auto/ultimate/performance/efficient
改动:
- 注册 ID =
model_prefix+ display_name(拿不到名称的 SKU 才退回 SKU,不编名字); Name字段仍保留上游 SKU,执行时通过内置别名表名 → SKU还原(Qwen3.8-Flash→qfmodel);- 模型目录按上游 SKU 去重:否则
extra_models: [gmodel]与实时清单会以两个 ID 注册同一个 SKU; - 修掉一个既有缺陷:
model_mapping里写带前缀的键(qoder-qfmodel: xxx)以前永远匹配不上, 因为执行路径先stripModelPrefix再查表;现在带前缀键按裸键登记,且优先于内置别名; - 管理页模型表改为「注册 ID(模型名)| 上游 SKU | 上下文 | 最大输出」。
兼容性(实测边界,务必按这个说):插件侧仍认旧 SKU(qfmodel 直通上游),但宿主的模型路由表
只含新 ID,所以旧 ID 在宿主层就被拒:
POST /v1/chat/completions {"model":"qoder-qfmodel"}
→ 400 {"error":{"message":"unknown provider for model qoder-qfmodel","code":"model_not_found"}}
POST /v1/chat/completions {"model":"qoder-Qwen3.8-Flash"}
→ 200 内容="好" 模型回显=Qwen3.8-Flash
插件日志: upstream queues model qfmodel (p3); waiting 30s … → [Chat] 完成 finish=stop
也就是说:升级后客户端要改模型名(对照表见 README)。需要兼容旧名字时用宿主的
oauth-model-alias(name: <新模型ID> / alias: <旧名字>)逐条指过去。
测试:TestModelIDsUseHumanReadableNames(ID 是名称 + Name 保留 SKU + 名称/SKU/关键字三种
请求都能还原)、TestUserMappingOverridesDisplayAlias(带前缀映射键生效且优先于内置别名)、
目录去重用例(裸 SKU ID 不再注册、同一 SKU 不重复)。
按下面步骤装好并跑通(凭证与日志类文件都被 .gitignore 忽略):
auths/下的多账号导出文件(600)—— 原件未改动(md5 核对一致); 宿主首次成功刷新后会把它改写成扁平单账号格式(既定行为)。plugins/qoder2api.so、bin/cpa-server、config.yaml(port 18318 / auth-dir auths / 插件启用)。- 管理密钥明文会被宿主启动时哈希回写,故明文另存为独立文件(600)。
- 管理页:
http://127.0.0.1:18318/v0/resource/plugins/qoder2api/console。 - 验证完成后已停服(端口释放、
plugin unloaded);启动命令写在 README。
宿主 internal/api/handlers/management/plugin_quota.go 组装 QuotaFetchRequest 时
只带 AuthIndex/AuthID/Metadata/Attributes;插件又不把 token 放进 metadata/attributes,
而 handleQuotaFetch 只解析 StorageJSON → 真实部署里额度查询直接 502
(auth storage has no token field ... and no secret blob)。现在额度/刷新/执行三条路径统一走
resolveRPCCredential:优先用宿主给的 storage,缺失则按 auth_index(必要时用 auth_id 经
host.auth.list 映射)回查;host.auth.get 只认 index,不能拿 id 直接当 index 用。
同一处还把 model.for_auth、执行路径的凭证解析改成 bundle 感知,避免多账号导出文件被误判。
账号:一个国际版 Qoder 账号(已脱敏),区域 global,OAuth(device + refresh token)。
| 项目 | 结果 |
|---|---|
| 导入 | 1 账号,region=global、auth_mode=oauth、email 正确 |
| 凭证有效性 | 宿主驱动刷新 {"success":true}(插件内 userinfo 200) |
| 真实上游模型清单 | POST /plugins/qoder2api/models/refresh → 200,15 个模型 |
| 模型注册 | /v1/models → 19 个 q2-*(15 实时 + 别名/兜底) |
| 真实额度 | 200:plan=Free、userQuota.total=0、isQuotaExceeded=true |
| 非流式对话 | 上游 403 code 112(pricingUrl)→ 插件转 403 insufficient_quota |
| 流式对话 | 事件流打通,上游业务错误原样回传 |
| 免费模型 Qwen3.8-Flash 非流式 | 200 + 真实内容(你好,finish_reason=stop,tokens≈13.2k),中途排队 130 秒后成功 |
| 免费模型 Qwen3.8-Flash 流式(OpenAI 协议) | 200,21 个分片全部可解析,0 个不可解析,内容完整 |
免费模型 Qwen3.8-Flash 流式(Claude /v1/messages) |
200:message_start→content_block_start→ping→content_block_delta(text=你好)→content_block_stop→message_delta→message_stop,中途排队 30 秒后成功 |
| 免费模型排队耗尽预算 | 503 + qoder_model_busy(消息明确“不是额度或凭证问题”) |
| 签到 | 打对区域(不再 401),返回 no_campaign(国际版无签到计划) |
| 稳定性 | 无 panic/fuse,日志不含凭证 |
| 本仓库部署 | 9/9 冒烟通过(含修复后的额度路由);管理页 6 个接口全 200;/console 返回 HTML |
关键结论:链条已完整打通到上游并正确回传上游语义。该账号是 Free 计划、额度 0
(isQuotaExceeded: true),所以上游对任何推理请求回 403 引导升级——这是账号额度状态,
不是插件缺陷;换一个有额度的账号即可出字。
- GitHub Actions 在 5 个平台构建全绿(linux/amd64、linux/arm64、darwin/amd64、darwin/arm64、windows/amd64)。
gofmt -l干净、go vet ./...干净、go test ./... -count=1全绿(含新增回归测试)。- 真实宿主集成冒烟 11/11 通过(加载/模型/账号/上游清单/额度/刷新/签到/非流式/流式/无泄漏/无 panic)。
- 多账号导出文件被 CPA 标记为“插件虚拟账号”:不能在面板单独编辑/删除,刷新也不会写回该文件; 单账号导出在首次成功刷新后会被 CPA 改写成本插件扁平格式(一条账号一个文件)。
- 国际版账号:无每日签到;额度与计费走
openapi.qoder.sh。 - 插件状态目录默认
~/.qoder2api-plugin(machine_salt、state.json、可选日志)。 本轮测试写入的临时设置(auto_checkin)已恢复默认,测试用账号记录已清理,原文件备份为state.json.bak-before-cleanup。
- 付费模型(
qmodel_38max/dmodel/gmodel等)的成功推理:该账号 Free 计划额度 0, 上游一律 403code 112(pricingUrl);免费模型qfmodel已实测成功。 - 签到成功领取(国际版无签到计划,返回
no_campaign)。 - 多账号同时故障转移;Windows/macOS 构建。
- Codex(
/v1/responses)协议的真实流式:分帧已按宿主契约实现并有单测, 但本轮只对 OpenAI 与 Claude 两条协议做了真实客户端验证。