Repository navigation
Conversation
Qiniu (七牛云) MaaS / Modelink, provider id "qiniu": sign in with one sk- API key and use the 89 models Qiniu serves, including the free tier of the 1024 activity's Conatus. - config declares the provider with a built-in 89-model snapshot - provider.models refetches /v1/models at run time and merges over it - auth.loader turns a dead key into X-Magpie-Sign-In: expired - auth.usage reads today's token spend through Qiniu's management API, signed with an optional AccessKey/SecretKey asked at sign-in No runtime dependencies. 45 node:test cases, all offline.
9c31020 to
b8885de
Compare
Qiniu's /ai/inapi/v3/stat/new names the billing item in `key` (cache/ncache/output) and the unit in `unit_name`, and its `total` is counted in `unit_size_default` tokens (1000). Reading `name`/`unit` yielded undefined, so tokens stayed 0 - and since the percentage is derived from it, the usage card sat at "100% remaining" no matter how much was actually spent. Now all three items are summed with the right multiplier. Verified against the live endpoint: today's spend parses to 2.08M tokens. Also adds a regression test pinning the real response shape, and sumUsage now accepts modelFilter so per-model numbers stay correct.
Brings the branch up to date with magpie-community/plugins main (trae 0.2.22 and earlier) so the PR no longer reads as diverged. Only packages/qiniu/ and the qiniu entry in registry.json are ours; every other file is taken from upstream byte-for-byte.
|
Hi, a note in case it helps while this is waiting for review. CI hasn't run. What I verified locally in the meantime:
One thing worth flagging for review — Qiniu's usage endpoint uses non-obvious field names, which I got wrong on the first pass and fixed in Reading I also merged upstream Happy to change anything you'd rather see done differently. |
yetone
left a comment
There was a problem hiding this comment.
感谢提交!我逐行看了 4df3c24,七牛云是云厂商,可以收。下面这些问题需要先改:
已跑
- 沙箱 HOME 下
bun test packages/qiniu:49 pass,fetch 全部是 stub,不会联网。bun scripts/check.mjs qiniu:✓。 - 用 magpie 插件宿主跑
scripts/try.sh qiniu:能加载,列出 89 个模型,状态是未登录。我没有七牛账号,所以没发过真实请求。
必须改
- 每日额度是编出来的(index.mjs 里的
dailyLimit: Number(opts.dailyQuota) || DEFAULT_DAILY_QUOTA)。不填dailyQuota的话,每个用户都会拿到一个 1e8/天的窗口,used也会按百分比算。这和 usage.mjs 的注释、PR 说明里的「不填就只显示已用量」「never invents a balance」对不上。还有几个问题:- 活动页写的是「认证用户每日 1 亿 Token,充值后最高 10 亿」,只算免费模型,活动到 2026-11-30 23:59 结束。现在的累计没设
usageModel时把所有模型的 token 都算进去了; - 窗口到 100% 时,magpie 的路由会把这个账号挂起。充值用户、用付费模型的用户都会被误伤;11-30 之后这个额度就完全是编的了。
改法:不填就不给 limit,只报已用量(不带used百分比)。百分比只在用户自己填了dailyQuota时才出,而且只算活动覆盖的那个模型。
- 活动页写的是「认证用户每日 1 亿 Token,充值后最高 10 亿」,只算免费模型,活动到 2026-11-30 23:59 结束。现在的累计没设
- scripts/patch-keys-url.mjs 直接改写
~/.config/magpie/providers.json。这个脚本虽然不打进包(不在files里),但 README 让用户自己去跑。插件不能写 magpie 的配置文件,请删掉这个脚本和 README 里对应的那段。需要链接的话,放在插件自己的 provider 元数据里。 - 推理模型判断误伤 instruct 模型(
REASONING_RE)。像qwen3-coder-480b-a35b-instruct、qwen3-235b-a22b-instruct-2507这种 instruct 模型,因为名字里有qwen3就被标成了推理模型。请排除instruct,或者按七牛 API 返回的字段判断。
应该改
- AK/SK 会退回环境变量
QINIU_ACCESS_KEY/QINIU_SECRET_KEY(index.mjs:85-87)。登录时填的 AK/SK 已经够用了,再退回环境变量的话,同一个变量会套到每个没填 AK/SK 的账号上,查出来的是别人的用量。GUI 启动的 magpie 也读不到 shell 变量,README:155 的export写法在那种情况下没用。请去掉环境变量这条退路,README 也一起改。 - 市场里的名字叫「七牛云 · Conatus Free」,绑的是一个 11-30 就结束的活动。请改成「七牛云」,活动的事写在描述里。
- 小问题:模型 key 去掉了厂商前缀,冲突时才退回完整 id,所以同一个模型的 key 取决于列表顺序,七牛加了一个同名模型,旧 key 就可能变。建议直接用完整 id,或者让去前缀的规则和顺序无关。
Review feedback on magpie-community#374, all of it fair: * dailyQuota no longer has a default. Qiniu never returned a daily limit, so a built-in 1e8 handed every user a made-up "100% remaining" window - and at 100% magpie suspends the account, which would hit paying users and anyone on a paid model. Without dailyQuota the window now reports used tokens only, with no limit and no used percentage. When dailyQuota *is* set, only the free models are counted, because the activity's allowance covers those alone (and ends 2026-11-30). * The AK/SK env-var fallback is gone. One QINIU_ACCESS_KEY would be applied to every account that left the prompts blank, so the usage shown could be someone else's; a GUI-launched magpie cannot see shell variables anyway. Credentials now travel with the account only. * scripts/patch-keys-url.mjs removed, and the README with it. A plugin must not rewrite magpie's providers.json; the key URL is declared in the provider metadata and the host decides how to render it. * instruct models are no longer flagged as reasoning models. Names like qwen3-coder-480b-a35b-instruct matched /qwen3/ and were marked as thinking models; an explicit instruct exclusion now wins over the family patterns. * Model keys are order-independent. The old "fall back to the full id on collision" rule depended on list order, so the same model list could yield different keys. Keys are now planned over the whole sorted id set. * Provider name is 七牛云 rather than the activity's name; the activity is described in the summary instead. 59 node:test cases (was 49), all offline. Each fix has a regression test that fails when the fix is reverted.
The entry was named after Conatus Free, but that is an activity ending 2026-11-30 - the provider is 七牛云 and outlives it. The free models are described in the summary instead.
Primes the PR's diff base so the qoder QwenWork change that landed upstream after the previous merge is no longer attributed to this PR. Only packages/qiniu/ and the qiniu entry in registry.json come from the branch; every other file is taken from upstream byte-for-byte.
|
Thanks — all six points were right, and the quota one was a real bug, not just an inconsistency. Fixed in The invented daily quota (必须改) Agreed, and I'm sorry for shipping it.
Deleted, along with the README section. You're right that a plugin has no business rewriting magpie's Reasoning vs instruct (必须改)
const hasReasoning = (id) => {
if (INSTRUCT_RE.test(id)) return false
return REASONING_IDS.has(id) || REASONING_RE.test(id)
}The four offenders in the live catalogue are fixed: Env-var fallback (应该改) Removed. Provider name (应该改)
{ "package": "@magpie-community/opencode-qiniu-auth", "name": "七牛云", "providers": ["qiniu"], "community": true,
"summary": { "en": "…Models covered by the 1024 Programmer's Day activity (free until 2026-11-30) are marked free…" } }Key order-dependence (小问题) Fixed by planning keys over the whole sorted id set instead of accumulating as we go: export function planKeys(ids) {
const list = [...new Set(ids.map(String))].sort()
// 短名出现一次才采用,否则该 id 保留完整 id
}Same list, any order, same keys. Test asserts forward/reverse/shuffled all produce identical key sets. I kept the short-name convention rather than switching to full ids (it's what users type), and documented the residual: if Qiniu later adds a colliding name, that one short key reverts to the full id — unavoidable for any de-prefixing scheme, and Also Merged upstream again so the diff base primes past the qoder/QwenWork change — Tests: 49 → 59. Each fix has a regression test that fails when the fix is reverted — I checked by reverting them:
Still no runtime dependencies, still all offline. Happy to split this into a smaller commit series, or to drop the model-key change entirely if you'd rather see full ids — say the word. |
|
Thanks for this! I reviewed it at 2b760b8, merged onto current main. Qiniu is a cloud serving models, its own Conatus among them, so it belongs here as a provider. What I ran, in a sandbox HOME with the proxy pointed at a dead port:
Three things to fix before it can merge:
Not tried: a request with a real key, and the AK/SK usage read. I have no Qiniu account. A one-line note of a real run of each in the PR, once these are fixed, would help. |
…op offline models The three blockers from review, plus the activity tier that replaces the hand-typed 100000000. * None of the free models was shown to agents. magpie hands agents the first 24 models of a vendor's list, and both the snapshot and provider.models were sorted by id — so bytedance/… and deepseek/… filled all 24 slots and qiniu/conatus-free, kimi-k3-free, glm-4.5-air-free and nemotron-…-free fell outside. Those are the models this plugin and its registry entry are about. Ordering is now explicit: free models first (qiniu/conatus-free pinned at magpie-community#1), then the flagships, then the rest by id — in lib/catalog.mjs (regenerated, so the file itself carries the order) and in what provider.models returns. selectModels sorts on the way out, so no caller can skip it. * Every account without AK/SK showed an error. The prompts are optional, but auth.usage returned error: "未填 AccessKey/SecretKey…", so the account row read "Allowance unavailable: …" for everyone who skipped them, as if something were broken. It now returns { windows: [] } with no error field; the README explains how to add the pair. * Models Qiniu drops stayed listed forever. provider.models spread `known` as the base of the result, so any snapshot id was offered even after it left /v1/models, and requests to it would fail. The live list is now the source of truth and `known` only supplies per-model overrides for ids that are still there. Falling back to the snapshot when the live read fails is kept on purpose and marked as such. New test: a live list that lacks a snapshot id. * activityTier replaces the hand-typed quota. Qiniu returns no plan, verification state or recharge history through /inapi/v3/stat/new, so the plugin cannot tell which tier an account is on — the user declares it: verified (1e8/day) or verified-plus (1e9/day, verified plus 100 CNY recharged). dailyQuota still wins when set, for quotas outside the activity. A wrong tier name raises instead of silently reading as "no quota". The activity ends 2026-11-30; after that the tiers stop yielding a limit rather than fabricating one. Registry entry: model count 89 → 91, and the summary now mentions activityTier and that free models are listed first. 78 node:test cases (was 59), all offline. Each fix has a regression test that was confirmed to fail when the fix is reverted. scripts/check.mjs passes; 91 built-in models, 4 free, 91 live. Still not tried, and I have no Qiniu account: a request with a real key, and an AK/SK usage read.
|
All three fixed in aec0b90. Free models now visible to agents. Ordering is explicit everywhere — lib/catalog.mjs (regenerated, so the file carries the order) and what provider.models returns. Free models first (qiniu/conatus-free pinned at #1), then flagships, then the rest by id. The first four slots are now conatus-free, kimi-k3-free, nemotron-3-ultra-550b-a55b0-free, glm-4.5-air-free — all inside the 24 an agent sees. selectModels sorts on the way out so no caller can skip it. No AK/SK is no longer an error. auth.usage returns { windows: [] } with no error field; the README explains how to add the pair. Dropped models no longer linger. provider.models is driven by the live list; known only supplies per-model overrides for ids that still exist. Falling back to the snapshot when the live read fails is kept on purpose and marked as such. Added a test with a live list that lacks a snapshot id. Plus activityTier. The usage endpoint returns no plan, verification state or recharge history, so the plugin can't tell which tier an account is on — the user declares it: verified (1e8/day) or verified-plus (1e9/day, verified + 100 CNY recharged). dailyQuota still wins when set. A wrong tier name raises instead of reading as "no quota". After 2026-11-30 the tiers stop yielding a limit rather than fabricating one. Registry entry updated too: 91 models, and the summary mentions activityTier and that free models are listed first. 78 node:test cases (was 59), all offline. scripts/check.mjs passes — 91 built-in, 4 free, 91 live. Each new test was confirmed to fail when its fix is reverted. On the two you couldn't try: I do have a Qiniu account and the AK/SK usage read works against the real /stat/new (已用 26.77M). The inference call with a real key is the one thing I have not run end-to-end — I won't claim it here. |
yetone
left a comment
There was a problem hiding this comment.
Reviewed at aec0b90.
What was run
bun test packages/qiniuin a sandbox HOME/TMPDIR: 78 pass, 0 fail.bun scripts/check.mjs qiniu: ok. The branch merges cleanly into main.npm view @magpie-community/opencode-qiniu-auth: E404, so it has never been published.- I probed the plugin with a fake fetch:
- Without
activityTier/dailyQuota, the usage window has noused, so it shows as a text row. - With
activityTier, the window hasused,limitandmodel: "-free", and routing narrows it to the free models. That part is right. - A 401
auth_api_key_invalidsetsx-magpie-sign-in: expired. - An account's own AK/SK take precedence over the plugin options. That part is right too.
- Without
The logic works. Before merging, these need changing:
-
Every user-facing string is Chinese only. This covers:
- the sign-in method label (
七牛云 API Key); - the prompt messages and placeholders;
- the usage window names (
今日已用,今日额度) and thedisplaytext; - every error message.
magpie runs in English too, so each string needs an English form. Either use English, or put both languages in the string as other packages do. The registry name
七牛云can stay; it was asked for earlier. - the sign-in method label (
-
Version.
package.jsonis at0.5.0, but the package has never been on npm. Please start at0.1.0, as we asked River to do. -
The README is out of date and Chinese only.
- It cites "39 个用例" and "49 个用例", but the suite has 78 tests now. Drop the counts.
- It says
plan/usageModeldefaults to-freeonly whendailyQuotais set, butactivityTiersets it too. - "Magpie 私有增至" should read 增值.
- Please add an English section, at least for install, the options and what the usage row shows.
-
AK/SK scope. The README says these keys have "权限极高": they are the whole Qiniu account's root credentials. If Qiniu lets you create a sub-account or IAM key limited to reading usage/stats, recommend that in the prompt and the README. If it doesn't, say plainly in the prompt that the key can manage the whole account and is optional (it is only used for the usage row).
Happy to merge once 1–3 are done.
|
Hi @lakegan —— 这个 PR 你还继续推吗? 距上次活动大概 3 天了(你最后的提交 我想把七牛云 MaaS 加成我自己插件里的一条渠道( 所以动手前先问一句:
不管哪种我都想先问清楚再动手,免得白写一遍或者撞车。 @yetone —— 同步一下这件事,避免两个七牛云入口不小心都进 registry。 |
Add Qiniu MaaS provider plugin
What it does
A provider plugin for 七牛云 MaaS (Modelink). Sign in with one API key and use
the 89 models Qiniu serves — DeepSeek, Qwen, GLM, Kimi, MiniMax, Doubao, Xiaomi
MiMo and Qiniu's own Conatus. The 1024 Programmer's Day activity
(details) makes a few of them
free until 2026-11-30; the plugin marks those models
free: truebut does notdepend on the activity.
qiniu, display name 七牛云sk-…from https://portal.qiniu.com/ai-inference/api-key@ai-sdk/openai-compatible),https://api.qnaigc.com/v1How it is built
configdeclares the provider (Qiniu is not in models.dev) with a built-insnapshot of 89 models, so models are listed even offline.
provider.modelsrefetchesGET /v1/modelsat run time (that endpoint isanonymously readable) and merges over the snapshot. If it fails, the host keeps
the declared list.
auth.loaderwatches for401+auth_api_key_invalidand answers withX-Magpie-Sign-In: expired, so a dead key marks the account for re-sign-ininstead of failing over like an ordinary request error.
auth.usage(magpie's own hook, ignored by OpenCode) reads today's tokenspend through Qiniu's management API
GET https://api.qiniu.com/ai/inapi/v3/stat/new, signed HMAC-SHA1 with anoptional AccessKey/SecretKey pair asked for at sign-in and kept in the
account's metadata.
Notes worth knowing
amounts. So there is no default quota: without
dailyQuotathe windowreports used tokens only, with no
limitand nousedpercentage. A built-in1e8 would have handed every user a made-up "100% remaining" window, and magpie
suspends an account once a window reaches 100% — which would hit paying users
and anyone on a paid model, long after the activity ends. When
dailyQuotais set, only the free models are counted, since the allowance covers those
alone.
is in
key(cache/ncache/output), the unit is inunit_name, andtotalcountsunit_size_defaulttokens (1000) rather than single tokens.Reading
name/unityieldsundefinedand leaves the spend at zero, whichmakes the usage card sit at "100% remaining" forever — the parsing and a
regression test pinning the real response shape are both in
lib/usage.mjs/test/usage.test.mjs.(Asia/Shanghai). When it runs out Qiniu rejects the call outright rather than
switching to a paid model, so the plugin does no automatic fallback.
/v1/modelsreturns only
context_lengthandmax_tokens. An explicitinstructexclusion wins over the family patterns, so
qwen3-coder-480b-a35b-instructis not mistaken for a thinking model. Users can override any model in
plugins.json.auth.methodscarries two optional prompts (accessKey,secretKey) so theAK/SK can be entered in the sign-in dialog instead of a config file. Both may
be left empty. Qiniu's model API key (
sk-…) and its account AK/SK aredifferent credentials and are never mixed: the key signs model calls, the
AK/SK only reads usage. Credentials travel with the account — there is no
environment-variable fallback, because one
QINIU_ACCESS_KEYwould be appliedto every account that left the prompts blank.
provider metadata (
keysUrl) and the host decides how to render it.yields the same keys regardless of the order Qiniu returns it in.
Testing
59
node:testcases, all offline withfetchstubbed:sign.mjsverified against the example inhttps://developer.qiniu.com/kodo/1201/access-token — byte-for-byte match
(
MY_ACCESS_KEY:1uLvuZM6l6oCzZFqkJ6oI4oFMVQ=)./stat/newresponse shape, the no-default-quotacontract (
limit/usedabsent unlessdailyQuotais set), the free-modelfilter, the absence of an env-var fallback, window shape, and the credential
mix-up guard (a
sk-key entered as SecretKey is reported before any requestis made).
instructexclusion, order-independent keys, and filtering.config/auth/provider.No runtime dependencies; only
node:cryptoand globalfetch.Registry entry
{ "package": "@magpie-community/opencode-qiniu-auth", "name": "七牛云 · Conatus Free", "providers": ["qiniu"], "community": true, "summary": { "en": "…", "zh": "…" } }Added to
registry.json; the package itself lives inpackages/qiniu/.