- TypeScript 100%
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 |