npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zenodinh/pi-provider-qoder

v0.1.3

Published

Pi extension for Qoder AI — with OAuth authentication, COSY signatures and WAF bypass

Readme

pi-provider-qoder

A pi extension that connects pi to Qoder. It emulates the official qodercli (and its China variant) protocol end-to-end: OAuth device login / PAT exchange, COSY request signing, the live model catalog, and the streaming chat gateway — no standalone CLI binary required. Verified against pi 0.87.1.

pi install npm:@zenodinh/pi-provider-qoder
# or: omp install npm:@zenodinh/pi-provider-qoder
# or straight from the repo: pi install git:github.com/zenodinh/pi-provider-qoder
# remove it later with: pi remove npm:@zenodinh/pi-provider-qoder

Quick start

Inside pi, log in first:

/login qoder

Then pick a model — or start directly from the command line once logged in:

pi --provider qoder --model Qwen3.8-Max
pi --provider qoder-cn --model Qwen3.7-Plus

Features

  • Two regions registered together — qoder (global) and qoder-cn (China) in a single extension.
  • Two auth flows — paste a PAT, or (global only) sign in through the browser device flow.
  • Startup auto-login — a PAT found in the environment logs the provider in at startup, before the first request.
  • Live model catalog — fetched from the authenticated Qoder /model/list endpoint and cached per region.
  • Effort-aware thinking — pi's thinking levels are mapped onto each model's enable_thinking / reasoning_effort support.
  • Agentic tool use — native tool calls, plus DSML markup embedded in the text stream, parsed into clean toolCall blocks.
  • Robust streaming — handles Qoder's double-[DONE] SSE envelope, hidden <thinking> markup, idle timeouts, and orphaned/compacted tool history.
  • Usage reporting — /qoder-quota opens an interactive panel (plan + add-on credits, remaining amounts, renewal date, usage-page link) in the TUI, and prints the same report everywhere else — on demand, with a 60 s cache.
  • Cache warming health — /qoder-cache reports whether the opt-in warm refreshes are actually keeping the prompt cache alive: refresh count and spend, cache-read share, natural-gap survival, and the learned lifetime/rate profile — read from the always-on session ledger, no debug flag required.
  • WAF bypass / COSY signatures — every request is signed with the same RSA/AES machine-bound headers Qoder expects.

Providers

Both providers register together; each is a separate region with its own account, catalog, and cache.

| | qoder (global) | qoder-cn (China) | | --- | --- | --- | | Chat gateway | https://api3.qoder.sh/ | https://gateway.qoder.com.cn/ | | Login | /login qoder — browser OAuth or PAT | /login qoder-cn — PAT only | | PAT page | https://qoder.com/account/integrations | https://qoder.com.cn/account/integrations | | Account page | https://qoder.com | https://qoder.com.cn | | Env PAT (first match) | QODER_API_KEY, QODER_PERSONAL_ACCESS_TOKEN, QODER_PAT | QODERCN_API_KEY, QODERCN_PERSONAL_ACCESS_TOKEN, QODERCN_PAT | | Supports browser login | ✅ | ❌ |

Authentication

Qoder PATs (pt-...) cannot authenticate API calls directly. Every flow ultimately exchanges one for a short-lived job token (jt-...), optionally alongside a job refresh token (jrt-...):

  1. Paste a PAT — both regions prompt for a PAT and exchange it for a job token.
  2. Browser OAuth (global only) — leave the PAT prompt empty and pi opens a Qoder sign-in URL; the plugin polls until you approve. This flow is a PKCE device grant.
  3. Environment variable — set one of the PAT env vars above and the provider logs in automatically at startup. An explicit env PAT is authoritative and is re-exchanged on every startup (so an old cached token never silently shadows a new one).

A job token is short-lived; when it nears expiry the provider transparently refreshes it — by re-exchanging the stored PAT, or by using the job refresh token. Global browser and CN logins both persist credentials through pi. Refresh failures are reported rather than extending an invalid token's local expiry; HTTP 401/403 asks you to log in again.

Authentication, catalog and quota requests have a 15-second deadline covering headers and body. Browser login has a three-minute overall deadline. Login/refresh and chat identity resolution honor cancellation; chat also honors the host's optional timeoutMs in addition to the stream idle timeout.

Credential storage

Credentials are managed by the host (pi uses ~/.pi/agent/, or PI_CODING_AGENT_DIR; OMP uses its own store). Identity lookups are cached only in memory and never overwrite credentials:

  • auth.json — the exchanged credentials (per provider), plus the resolved user identity (userID/email/name/machineID).
  • The machine id used for COSY headers is read from ~/.qoder/.auth/machine_id or, if absent, ~/.pi/agent/qoder-machine-id (created if missing).

Qoder credits are billed per account; only the region you log into is affected.

Models

After login, /model (or pi --list-models) lists what that region offers.

Catalog source. At startup and on each new session the provider checks its model cache. If it is missing, older than one hour, or was fetched for a different account it re-fetches the authenticated region catalog from /model/list and writes a fresh copy to:

  • ~/.pi/agent/qoder-models-cache.json (global)
  • ~/.pi/agent/qoder-cn-models-cache.json (China)

Fallback catalog. When the live catalog is unavailable, the four permanent tier aliases (Auto, Ultimate, Performance, Efficient) are offered so a model can always be picked offline. Model-named rows are deliberately not kept: a Qoder release renames them, and a dead id is only discovered at dispatch — the live catalog supplies every other model, and each one's context window, as soon as a fetch succeeds.

Model ids. The pi-visible id is the server display_name with all whitespace stripped (e.g. Qwen3.8-Max, Kimi-K3). A hidden upstreamKey (lite, qmodel, qmodel_latest, dmodel, …) is kept internally and sent to the gateway on each request.

Context & output.

  • Context window: taken from the largest context option the catalog advertises (e.g. 1M when 200K/400K/1M are offered). When the catalog omits context options, a 1M fallback is used for most models; a few models are pinned lower to what they actually advertise (e.g. Kimi 256K, and several CN models 200K).
  • Output: max_tokens goes on the wire only when it has an owner — a cap the catalog advertises (max_output_tokens), a value you set on the model, or pi's per-request cap (e.g. compaction at 40K). Without an owner the field is absent and Qoder's own default applies; qodercli's bodies drop it the same way. pi's answer-room and compaction math use the advertised cap, else 32000.
  • Cost: Qoder bills in Credits, not USD, so pi's monetary cost is left at zero. The relative Credit multiplier (price_factor) is exposed as priceFactor on each model when the live catalog reports it, and the official per-request credits / original_credits / billable fields are preserved on the runtime usage object when Qoder reports them.

Thinking & effort

Models flagged as reasoning expose pi's thinking level picker (minimal, low, medium, high, xhigh, max, plus off). How they behave depends on what the catalog advertises:

  • Effort-based models (thinking_config.enabled.efforts) map pi levels to Qoder's reasoning_effort (low/medium/xhigh/max, …); unsupported levels are hidden from the picker.
  • Toggle-based models (thinking on/off only) treat any level as "on" and only send enable_thinking.
  • Disabling thinking sends enable_thinking: false so non-reasoning models don't reason by default.

The streamed response is normalized into pi thinking blocks regardless of how the server delivers it — a dedicated reasoning_content channel or inline <thinking> / <think> / <reasoning> / <summary> tags split across chunks.

Environment variables

| Variable | Purpose | | --- | --- | | QODER_API_KEY, QODER_PERSONAL_ACCESS_TOKEN, QODER_PAT | Global PAT (first non-empty wins). | | QODERCN_API_KEY, QODERCN_PERSONAL_ACCESS_TOKEN, QODERCN_PAT | China PAT (first non-empty wins). | | QODER_STREAM_IDLE_TIMEOUT_MS | Stream idle timeout override (default 120000 ms). | | QODER_STREAM_DELTA_INTERVAL_MS | Minimum gap between streamed text/thinking deltas (default 50 ms). Higher values cut UI CPU on long responses. | | QODER_DEBUG | Debug mode. When set, all diagnostics go to per-session JSONL files under ~/.pi/agent/logs/qoder-debug/<sessionId>.jsonl — every chat request (decoded body + redacted headers) and its raw server response, on both transports, plus best-effort failure notes (catalog refresh, PAT exchange fallthrough, userinfo lookup) and per-decision cache-warming verdicts. Nothing is written to the console/TUI. Malformed SSE and token refresh failures are always surfaced as errors regardless of this flag. | | QODER_DEBUG_DIR | Override the debug capture directory (default ~/.pi/agent/logs/qoder-debug). | | QODER_DEBUG_KEEP | Session files retained before oldest-first pruning (default 50). | | QODER_DEBUG_HEADERS | Set to 1 to include auth-bearing headers verbatim; by default Authorization/Cosy-*/token-shaped headers are redacted to their length. | | QODER_DEBUG_MAX_BYTES | Per-record cap for request bodies and response SSE text (default 2000000; longer values are truncated and flagged). | | QODER_FALLBACK | Set to 1 to retry a turn once on the legacy transport when the v2 model-server rejects a model key (stale routing); the correction is cached for the session. Off by default. | | QODER_PROTOCOL | Force v2 or legacy for every request, overriding the routing table. | | QODER_CORE_PLAN | Set to 1 to route every dispatch through the turn-plan producer (one awaited plan feeding both adapters the session forms, affinity placements, thinking inputs and capture metadata). Off (default) keeps the pre-migration dispatch path — an env-flip rollback kept for one release. The run-identity fixes are ungated and stay on with the gate off. | | QODER_CORE_STAMP | Set to 1 to wrap the legacy tail in the shared ordered terminal stamp (terminal-before-end guarantee, priced-only rate-source stamps). Off (default) keeps the pre-migration assembly-site stamps. Transitional, kept one release. | | QODER_MODEL_SERVER_HOST | Override the v2 model-server base URL (for example to reach a v2 host from the China region). | | QODER_CACHE_WARM | Set to 1 to approve pi's cache-warming refreshes on every provider, or 0 to veto them even where the approval file arms one (still requires pi's cacheWarming setting; refreshes spend Credits). Either value overrides qoder-warm-approval.json. With the gate on, refreshes are budget-governed per opportunity (QODER_WARM_BUDGET). | | QODER_WARM_BUDGET | Fraction of the protected cache miss an idle window may spend on refreshes (default 0.5); off disables the cap. Invalid values fall back to 0.5 with one debug entry. | | QODER_LEGACY_AFFINITY | Set to off to suppress every legacy cache-affinity placement (the probe-gated prompt_cache_key body field and the unsigned header trio) at request time, no rebuild — the rollback path for a promoted placement. | | QODER_LEGACY_CONTEXT_LENGTH | Set to off to remove context_length from the legacy request body at request time, no rebuild — the rollback path for the promoted top-level-number placement. |

How it works (protocol notes)

  • Request signing. The provider rebuilds Qoder's COSY authorization: an AES-encrypted user blob + RSA-wrapped key, an MD5 signature over path/body, and machine-bound Cosy-* headers (client type, OS, machine id/token). This is what lets it talk to the gateway directly.
  • SSE gateway. Chat streams from the Qoder /algo/.../agent_chat_generation endpoint. Qoder wraps events in an outer envelope with a JSON-string body, and can send the [DONE] sentinel both bare and wrapped — both are handled, plus a body that stays open after the sentinel.
  • Agentic "runs". Qoder groups billing/records per agentic run. The provider infers run boundaries from the message tail and reuses a run-scoped request_set_id + business (stable id/name, advancing init → start → processing) across tool rounds, so the credit ledger shows one aggregated entry per user prompt instead of many tiny ones.
  • Tool calls. Native structured tool_calls and DSML tool markup embedded in the text stream are both parsed into pi tool calls. Images returned by tools (e.g. screenshots, read) are forwarded to the model as data-URL image parts.
  • Prompt cache. A stable session id derived from your user id + model keeps prompt-cache affinity across consecutive requests in a session: the legacy envelope carries it as session_id, and v2 carries it both in metadata.context.session_id and — matching qodercli's OpenAI-protocol convention — as prompt_cache_key plus session_id / x-client-request-id / x-session-affinity headers. Recorded traffic shows the server-side lifetime is minutes (no large re-bill was observed at a 1-5 minute idle gap; the first deaths appear at ~6 minutes), so the provider declares promptCache.short = 300 s and can keep a session warm across short idle gaps (next bullet).
  • Cache warming (opt-in). With pi's "cacheWarming": "idle" setting plus QODER_CACHE_WARM=1, pi re-sends the last request with a one-token output cap before the declared lifetime expires, for up to 30 minutes of idle. Each refresh bills a cache read — measured ~50x cheaper than the re-billed input tokens it prevents (DeepSeek-Flash Credit usage, 2026-09-29) — and warming stops on context changes, the 30-minute idle cap, or anything pi considers unsafe to replay. The provider governs each opportunity: spend since the last real turn (USD, from Qoder's own Credits or priced v2 rows) must stay under QODER_WARM_BUDGET × the protected miss, models without a usable rate are priced from the worst economics ever observed (the conservative rung), and models with a published learned rate are capped by it. At session start the provider also learns per-model lifetimes (natural idle gaps only) and rate fits (R² ≥ 0.95) from the session ledger, writes them atomically to ~/.pi/agent/qoder-cache-lifetime.json, and re-registers the learned values. Run /qoder-cache for the health readout.
  • Warm approval file. Machines that never export QODER_CACHE_WARM can arm warming per provider instead: create ~/.pi/agent/qoder-warm-approval.json with {"version":1,"providers":{"qoder":true,"qoder-cn":false}}. Absent file = off (silent); any malformed shape rejects the whole file with one debug entry — valid keys are never salvaged past a corrupt sibling. Precedence is env over file over off, re-evaluated at every decision, so QODER_CACHE_WARM=0 is a per-process kill switch over an approved machine. Rolling warming back therefore takes BOTH: delete the file AND unset the env — either alone leaves the other armed. /qoder-cache shows both layers per provider, e.g. config: pi idle · gate qoder=ON(file) qoder-cn=OFF(off) · budget 0.5.
  • History repair. Before sending, orphaned tool results, dropped (error/aborted) assistant turns, and placeholderless tool-call messages are repaired so Qoder never rejects a request with "tool must follow a message with tool_calls".

Host request compatibility

The custom stream supports pi's onPayload (before encoding/signing), onResponse (before reading the body, including HTTP errors), injected fetch (chat and identity lookup), headers, env, timeoutMs, temperature, and model/request maxTokens. Header overrides are case-insensitive; null removes a default. Overriding COSY authentication headers can invalidate signatures. A model baseUrl override must point to a Qoder-compatible gateway, not a generic OpenAI endpoint. onProviderStreamEvent is honored on the v2 transport only: pi-ai's completions stream invokes the host hook for each parsed event before normalization, while the legacy transport does not call it.

Legacy turns now close each text block with a text_end event, matching v2's event vocabulary. The host maps every text_end to a message_update frame, so --json output gains one frame per text block on legacy.

Retry behavior

This provider never retries a chat POST inside its own fetch layer. src/retry.ts retries non-billable requests only — fetchWithRetry forces a single attempt for every non-GET method — so that layer never replays a POST. That GET-only policy is unchanged. Transport remains SSE. (The one repo-side re-dispatch is the opt-in QODER_FALLBACK self-heal, which fires only when the v2 model-server rejects a model key before a generation starts.)

Two retry layers sit above it, and they differ:

  • Agent-level retry (pi; both protocols). pi's agent loop retries a failed assistant turn by matching the rendered error text against isRetryableAssistantError. It is armed by default in pi (settings.retry.enabled), covers both the legacy COSY transport and v2, and re-dispatches the turn with bounded exponential backoff. This provider writes its error text to match that classifier, so a stream that ends without a terminal response event is retried where it previously was not.
  • Provider-level HTTP retry (pi-ai; v2's model-server calls). pi-ai can retry the HTTP request itself, but it is disarmed unless you set settings.retry.provider.maxRetries. Arming it silently retries v2's billable chat POSTs, which can duplicate billing and spend — it changes spend with no code change and no test. This is the tripwire: leave it unset unless you have measured the cost. The owner's settings leave it unset.

Usage reporting

Run /qoder-quota inside pi — on demand, never on the turn path. In the TUI it opens a compact panel (esc/q closes, r refreshes); every other mode prints the same report as a notification. It fetches the subscription quota and renders, in the Qoder desktop app's wording:

  • Plan Credits — used / total, used %, remaining, and the renewal date.
  • Shared Add-on Credits — the organization package (cap-shaped, with the legacy total fallback); shown as Unavailable when the org has suspended distribution.
  • Add-on Credits / Dedicated Credits — rendered when the account's payload carries them.
  • View details — a link to the account usage page (plus the payload's upgrade link when the quota is exhausted).

Repeat runs within 60 seconds are served from a cache; concurrent runs share a single request; r in the panel forces a refresh. Failures print a reason ("quota unavailable") instead of numbers.

Cache warming health

/qoder-cache answers "is warming actually working?" from the data that is always on disk — the session ledger and the learned profile — so no debug flag is needed. In the TUI it opens a panel (esc/q closes, r rescans); every other mode prints the same report. It shows the live config plus three evidence sections:

  • Refreshes — warm-refresh count, spend in USD (Qoder Credits ÷ 75, or the priced v2 total), the median cache-read share (a healthy refresh is a near-pure cache read; a low share means the replay re-wrote the prefix), and how many rows look like re-writes.
  • Survival — per model, the median cache-read share of real turns that follow a natural idle gap (warm refreshes excluded) and the count of probable misses. This is the signal that the cache survived the idle window warming was meant to bridge.
  • Profile — per model, the published learned lifetime and rate fit with sample count, age, and R², or the declared default when nothing has been published.

The report reads the newest session files first under a 1.5 s scan budget, and flags INACTIVE (no refreshes recorded), WARN (median refresh share < 0.9, or a model with ≥ 5 gaps and median survival < 0.8), or OK.

Per-decision detail — each verdict's reason, rate source, spend, and protected miss — requires QODER_DEBUG=1. Guard stops are not persisted to the ledger (only approvals materialize as cache_warm rows), so the stop rate is a debug-log metric; approvals, spend, and survival are always visible.

Development

npm install
npm test           # run the offline unit suite (replays recorded fixtures)
npm run test:coverage  # the same suite plus coverage/lcov.info
npm run test:live  # re-record live protocol fixtures (needs QODER_PAT / QODERCN_PAT)
pi -e ./src/index.ts  # load the extension from source in pi

See src/__fixtures__/live/README.md for the fixture format and how to re-record it.

Repository rules — the CI gates, the release process, and the host contracts to re-check on a pi upgrade — live in AGENTS.md.

Credits

This project is a fork of OnlyTomInSecond/pi-provider-qoder, which itself is a fork of simonsmh/pi-provider-qoder. Thanks to both for the protocol work this fork builds on.

License

MIT