README.md

prime-charm-hyper #

A Prime Agent provider extension for Charm Hyper, Charm's inference service for coding models.

Registers the hyper provider with the whole Hyper catalog, device-code subscription login, API-key auth, and the thinking controls each model advertises.

Install #

ln -s /Users/dawn/proj/prime-charm-hyper ~/.prime/agent/extensions/prime-charm-hyper

Edits are picked up by /reload; new sessions load it at startup. For a one-off run without installing:

prime-agent -e /Users/dawn/proj/prime-charm-hyper --model hyper/glm-5.3-flash

Auth #

/login hyper offers the subscription device-code flow first (POST /device/auth, then GET /device/auth/<code> until approved, then POST /token/exchange); the link and verification code are printed in the login dialog and the token is refreshed automatically. HYPER_API_KEY covers throwaway shells and the extension's own config resolves to it (!printf %s "$HYPER_API_KEY"), so when neither exists the provider reports an unresolved key rather than sending a placeholder as the credential. There is no key file.

Credentials live in Prime Agent's auth.json and are tried in list order, so the stored order is the precedence: subscription first, API key as the fallback that gets used when the subscription is missing, stale, or blocked. Verified both ways: a turn still completes with the API key replaced by a bogus value (subscription used), and with the subscription replaced by an expired one (API key used).

The access token lasts an hour. Every POST /token/exchange returns a rotated refresh token and invalidates the previous one, so the extension returns the exchanged credential and Prime Agent persists it; a rejected refresh is reported as "run /login hyper again" rather than as a generic HTTP error. Removing a single account happens in the /login provider list; prime-agent logout hyper drops every credential for the provider, including the API key.

Why Chat Completions #

Hyper exposes three chat endpoints. This extension uses POST /v1/chat/completions because it is the only one that carries the behaviour an agent needs, measured on the live service:

/v1/chat/completions /v1/messages /v1/responses
streaming text yes yes yes
tool calls yes yes yes
reasoning visible reasoning_content thinking block not emitted
can stop thinking reasoning_effort: "none" no n/a
prompt-cache accounting prompt_tokens_details.cached_tokens none none
usage in stream yes, with cost yes yes

/v1/messages returns a thinking block even when the request omits thinking, and reports no cache fields, so cost accounting would be wrong. /v1/responses never emitted a reasoning item at any effort level. Both are usable; neither is better for this client.

One endpoint quirk worth knowing: tool_choice: "required" and the object form both return HTTP 400, while auto and none work. Prime Agent never sends tool_choice, so ordinary tool use is unaffected.

Models #

Metadata comes from models.dev's hyper entry, which carries what a coding agent needs and Hyper's own endpoints only partly report: the reasoning effort levels this deployment takes, vision support, context and output limits, and prices. GET /v1/models - public and documented - supplies membership and this deployment's own prices, and it agrees with models.dev on everything except display names.

catalog.json is that join, committed, and read at startup. On load the extension registers the newest available catalog - the committed one, or the last refresh cached in ~/.prime/agent/hyper-catalog.json - and then refreshes in the background, re-registering if the model list changed. Startup never waits on the network, and a fresh clone has models before the first request completes.

Regenerate after a model is added or repriced:

npm run regen              # two requests: models.dev and /v1/models
npm run regen -- --dry     # print the table without writing

Thinking #

Hyper's effort levels are per model, and Prime Agent's are not, so each advertised level maps to the level of the same name; a level the model does not advertise is hidden rather than sent on the chance that Hyper ignores it, because two settings that send the same request are worse than one setting. Models that reason without naming levels (glm-5, kimi-k2-thinking, gemma-4-26b) get a single on state, Prime Agent's max; a model that does not reason at all gets no thinking controls.

The off switch is only offered where Hyper advertises one, which arrives as a none level and goes out as reasoning_effort: "none". Everything else sends plain OpenAI fields: reasoning_effort for a level, nothing for off. Hyper also accepts a non-standard thinking: {type: "disabled"} on many models that do not advertise none - measured working on deepseek-v4-* and glm-5.x, and a hard 400 on glm-5.3 and kimi-k2-thinking - but that needs per-model evidence, and the advertised level list is the contract this extension ships against.

Caching and cost #

Prompt caching is implicit and automatic: Hyper bills a repeated prefix as a cache hit (measured at 4x lower cost on the second call) and reports it in prompt_tokens_details.cached_tokens, which the openai-completions provider turns into Prime Agent's cacheRead usage. No cache-control markers are sent; /v1/messages is where those would go, and it does not report cache usage at all.

/hyper prints the account's hypercredit balance (GET /v1/credits) and the registered model count. Hyper extends the standard usage object with cost.usd, cost.hypercredits, and remaining.hypercredits; Prime Agent reads only token counts and the catalog's published prices, so the balance in /hyper is the live figure.

Verifying #

npm test           # offline: key precedence, registration, catalog -> model config derivation
npm run verify     # live: catalog vs /v1/models and models.dev, streaming, usage, cache, tools, vision
npm run smoke      # one real turn through prime-agent with a hyper model
npm run check      # tsc --noEmit against a neighbouring prime-agent checkout

verify costs about twenty requests and takes ten seconds. It checks the derivation, compares every catalog model against both live sources, and exercises streaming, usage, cache accounting, tool calls, reasoning output, image acceptance and rejection, and the credit endpoint.

The device login was checked as far as it can be without a human: POST /device/auth returns the code and verification URL this extension renders, and an unapproved code polls as authorization_pending. The final token exchange follows the shape the official client uses and needs one approved login to exercise.

Relationship to the official extension #

Charm publishes @charmland/pi-hyper-provider for upstream Pi. This one is independent and targets this fork's extension API. Notable differences: this extension refreshes the catalog in the background instead of during startup, takes model metadata from models.dev, and has no status-line integration.

Layout #

file role
index.ts extension entry: registration, background refresh, /hyper command
catalog.ts /v1/models + models.dev parsing, catalog files, model-config derivation
oauth.ts device-code login and refresh-token exchange
hyper.ts provider identity, paths, key resolution, credit balance
http.ts JSON transport with timeouts and error extraction
catalog.json committed model list
regen.ts rebuilds catalog.json
test.ts offline checks
verify.ts live verification