Repository navigation
fix(chat): route agent chat to the account's remote-agent node - #48
Conversation
Qoder tracks the daily chat allowance per node. For accounts whose CLI node is the remote-agent node, every chat request to the fixed inference node is rejected with `403 code 110 "Billing daily count exceeded"` while the same account and model succeed from qodercli. - decode Qoder's custom Base64 responses (`qoderDecodeBody`) - resolve the account node map from `region/endpoints` at startup, cache it for one hour in `~/.pi/agent/qoder-endpoints-cache.json`, and route chat to the remote-agent node while the catalog stays on the inference node - on code 110, switch through the remaining nodes once each and persist the node that actually served the request Discovery failures fall back to the previous fixed gateway, so accounts that already work are unaffected.
- stream: the 110 daily-limit rejection can also arrive as a plain HTTP error instead of an SSE envelope; rotate nodes for it the same way (no model output has been seen when headers arrive, so the retry is safe). Covered by a stream-level test. - endpoints: let QODER_ENDPOINTS_CACHE_PATH override the cache file location, and point the endpoints/stream tests at a temp file. The tests previously wrote the user's real cache file and raced on it when run in parallel. - providers tests: raise the file timeout to 30s. Registration pulls in the full pi-ai compat layer; under parallel workers that can pass vitest's 5s default, and a timeout here also lets the late registration pollute the next test's registry counts.
899a6e1 to
30c993f
Compare
|
Verified this end to end, and pushed one follow-up commit (as maintainer, since edits from maintainers are allowed on this branch) — please take a look and say if you'd rather shape it differently: What I checked
What the follow-up fixes
Nothing in the routing logic itself needed changing — the candidate order, once-per-node rotation, partial-output guard, and fallback to the fixed gateway all check out. Thanks again for the fix! |
|
Merged — thank you! Squash-merged as Verification recap (beyond the suite): the decode is the exact inverse of the request encoding, the live node map on my account parses to infer |
Problem
On some accounts every
qoder/*model fails immediately withwhile the same account and the same model work from
qodercli, and the account'sCredit balance is untouched (
quota/usagereportsisQuotaExceeded: false, org packageat 42%). Error code 110 is documented by Qoder as the daily Chat allowance, i.e. a
per-account, per-node counter — not a Credit balance.
Root cause
Chat is pinned to the fixed inference gateway:
baseUrl: "https://api3.qoder.sh/"Qoder's node map, published per account by
region/endpoints, separates node classes:{ "inferNodes": ["https://api3.qoder.sh"], "dataNodeMap": { "remote_agent": ["https://api2.qoder.sh"], "codebase": ["https://api2.qoder.sh"] }, "security": ["https://api2.qoder.sh"] }The official CLI drives
agent_chat_generationagainst the remote-agent node, notthe inference node. Captured from
qodercli1.1.74 on the affected account:The extension sends the identical request (same path, same
Cosy-ClientType: 5, samebusiness.product: "cli", same machine id) toapi3.qoder.sh, so the daily chat counterfor that node is consumed by extension traffic and never shared with the CLI. Accounts
whose daily allowance on the inference node is exhausted get 110 forever, while the CLI
keeps working.
Change
qoderDecodeBody— inverse ofqoderEncodeBody.region/endpointsis returned inthe same custom Base64 alphabet/block order, so it was previously opaque.
src/endpoints.ts— fetch and parse the account node map at startup, cache theresolved chat node in
~/.pi/agent/qoder-endpoints-cache.json(1 h), and route chatto the remote-agent node while the model catalog keeps using the inference node.
403 code 110during a stream now rotates to the next candidate (remote-agent →inference → security) once per node and retries; the node that actually served the
request is persisted. A rejection after partial output is not retried.
Discovery failures, missing credentials, and non-parseable responses all fall back to
the previous fixed gateway, so accounts that already work are unaffected.
Verification
npm run check(tsc),npm run lint(biome),npm test— 436 tests pass, includingnew coverage for decode round-trips, node-map parsing, candidate ordering, cache TTL,
discovery fallbacks, rotation, and a stream-level test that replays a 110 envelope and
asserts the retry lands on the next node.
api2.qoder.sh, chat and tool calls succeed, and the node is cached. With the cacheremoved and the node map served fresh, the same result reproduces.
region/endpointsresponse captured from that account is committed assrc/__fixtures__/region-endpoints.json(public node names and IPs only).Notes
qoder-cn) keepsgateway.qoder.com.cn: its region has no equivalent node splitin the responses observed, and discovery falls back to the configured gateway.
models.yml-style override: the host resolvesbaseUrlper modelfrom the provider registration, so the provider itself has to resolve it.