@oh-my-pi/pi-catalog 18.1.18 → 18.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.1.19] - 2026-09-12
6
+
7
+ ### Added
8
+
9
+ - Added Charm Hyper as a built-in provider with API-key login, live model discovery, and per-model pricing, effort ladders, and limits read straight from its catalog ([#11656](https://github.com/can1357/oh-my-pi/pull/11656) by [@oldschoola](https://github.com/oldschoola)).
10
+
5
11
  ## [18.1.18] - 2026-09-11
6
12
 
7
13
  ### Added
@@ -13,6 +19,7 @@
13
19
  - OpenCode Go's DeepSeek Flash lanes (`deepseek-flash`, `deepseek-v4.1-flash`) now declare image input. The gateway serves them with vision despite the IDs carrying no vision suffix, so the class-wide `strip-image-input` rule was dropping attachments the endpoint reads; the modality is declared too, since live discovery seeds these lanes text-only ([#11774](https://github.com/can1357/oh-my-pi/pull/11774) by [@STRML](https://github.com/STRML)).
14
20
  - Amazon Bedrock OpenAI models, plus unclassified profiles such as opaque application-inference-profile ARNs, now carry the compatibility policy required to preserve image-bearing tool results ([#11681](https://github.com/can1357/oh-my-pi/issues/11681)).
15
21
  - DeepSeek V4.1 Flash requests now honor the documented 384K output maximum instead of being capped at 64K ([#11769](https://github.com/can1357/oh-my-pi/issues/11769)).
22
+ - Fixed the first-party `deepseek-flash` alias missing the V4.1 Flash wire contract: it now sends `max_tokens` with `reasoning_content` and replays reasoning and assistant content on tool calls with no tool choice ([#11799](https://github.com/can1357/oh-my-pi/pull/11799) by [@brit](https://github.com/brit)).
16
23
 
17
24
  ## [18.1.17] - 2026-09-10
18
25
 
@@ -1,4 +1,4 @@
1
1
  /** Every provider with an `auth/<id>.kdl` policy. */
2
- export type AuthProviderId = "abliteration" | "aiand" | "aimlapi" | "alibaba-coding-plan" | "alibaba-token-plan" | "amazon-bedrock" | "anthropic" | "azure" | "baseten" | "bedrock-mantle" | "cerebras" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "exa" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google" | "google-antigravity" | "google-gemini-cli" | "google-vertex" | "groq" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "meta" | "minimax" | "minimax-code" | "minimax-code-cn" | "mistral" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "synthetic" | "tavily" | "together" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
2
+ export type AuthProviderId = "abliteration" | "aiand" | "aimlapi" | "alibaba-coding-plan" | "alibaba-token-plan" | "amazon-bedrock" | "anthropic" | "azure" | "baseten" | "bedrock-mantle" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "exa" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google" | "google-antigravity" | "google-gemini-cli" | "google-vertex" | "groq" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "meta" | "minimax" | "minimax-code" | "minimax-code-cn" | "mistral" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "synthetic" | "tavily" | "together" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
3
3
  /** Providers whose policy declares a `login` flow (the `/login` roster). */
4
- export type LoginProviderId = "abliteration" | "aiand" | "alibaba-coding-plan" | "alibaba-token-plan" | "anthropic" | "baseten" | "cerebras" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "exa" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google-antigravity" | "google-gemini-cli" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "meta" | "minimax-code" | "minimax-code-cn" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "synthetic" | "tavily" | "together" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
4
+ export type LoginProviderId = "abliteration" | "aiand" | "alibaba-coding-plan" | "alibaba-token-plan" | "anthropic" | "baseten" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "exa" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google-antigravity" | "google-gemini-cli" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "meta" | "minimax-code" | "minimax-code-cn" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "synthetic" | "tavily" | "together" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
@@ -88,6 +88,14 @@ export declare const CATALOG_PROVIDERS: readonly [{
88
88
  readonly catalogDiscovery: {
89
89
  readonly label: "Cerebras";
90
90
  };
91
+ }, {
92
+ readonly id: "charm-hyper";
93
+ readonly defaultModel: "glm-5.3";
94
+ readonly envVars: readonly ["CHARM_HYPER_API_KEY", "HYPER_API_KEY"];
95
+ readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<"openai-completions", unknown>;
96
+ readonly allowUnauthenticated: true;
97
+ readonly dynamicModelsAuthoritative: true;
98
+ readonly skipCrossProviderReferenceFills: true;
91
99
  }, {
92
100
  readonly id: "cloudflare-ai-gateway";
93
101
  readonly defaultModel: "anthropic/claude-opus-4-8";
@@ -745,3 +745,22 @@ export interface CommandCodeModelManagerConfig {
745
745
  * or context window.
746
746
  */
747
747
  export declare function commandCodeModelManagerOptions(config?: CommandCodeModelManagerConfig): ModelManagerOptions<Api>;
748
+ /**
749
+ * Configuration for the Charm Hyper model manager.
750
+ *
751
+ * `baseUrl` overrides the gateway root for tests and self-hosted proxies; a
752
+ * value that omits the `/v1` surface gains one, so a host-only override
753
+ * behaves like every sibling provider's.
754
+ */
755
+ export interface CharmHyperModelManagerConfig {
756
+ apiKey?: string;
757
+ baseUrl?: string;
758
+ fetch?: FetchImpl;
759
+ }
760
+ /**
761
+ * Charm Hyper's gateway catalog. `/v1/models` is public and carries the live
762
+ * tariff, so discovery runs with or without a key and the snapshot is
763
+ * authoritative: a model the gateway stops serving is pruned rather than kept
764
+ * alive by a stale bundled row.
765
+ */
766
+ export declare function charmHyperModelManagerOptions(config?: CharmHyperModelManagerConfig): ModelManagerOptions<"openai-completions">;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Charm Hyper gateway endpoint, shared so a host migration — or a self-hosted
3
+ * proxy override — touches a single module.
4
+ */
5
+ export declare const CHARM_HYPER_API_BASE_URL = "https://hyper.charm.land/v1";
6
+ /**
7
+ * Resolve a configured Charm Hyper base URL onto the gateway's `/v1` surface.
8
+ *
9
+ * Every consumer must agree on this, because they key different things off the
10
+ * result: inference and discovery target it, `/usage` sends a bearer token to
11
+ * it, and the model cache namespace is hashed from it. Three separate copies
12
+ * previously disagreed for a blank value — the model manager treated it as
13
+ * absent and used the canonical host, while the others produced a bare `/v1` —
14
+ * so a whitespace-only override silently split inference, balance checks and
15
+ * caching across different endpoints.
16
+ *
17
+ * A blank or whitespace-only value therefore means "not configured" and
18
+ * resolves to the canonical host; anything else keeps its host and gains the
19
+ * `/v1` segment if it omits one.
20
+ */
21
+ export declare function normalizeCharmHyperBaseUrl(baseUrl?: string): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "18.1.18",
4
+ "version": "18.1.19",
5
5
  "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Stencil Labs, Inc.",
@@ -35,12 +35,12 @@
35
35
  "gen:proto": "bun scripts/generate-protocols.ts"
36
36
  },
37
37
  "dependencies": {
38
- "@oh-my-pi/omptype": "18.1.18",
39
- "@oh-my-pi/pi-utils": "18.1.18"
38
+ "@oh-my-pi/omptype": "18.1.19",
39
+ "@oh-my-pi/pi-utils": "18.1.19"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@bgotink/kdl": "0.4.0",
43
- "@oh-my-pi/pi-ai": "18.1.18",
43
+ "@oh-my-pi/pi-ai": "18.1.19",
44
44
  "@types/bun": "^1.3.14"
45
45
  },
46
46
  "engines": {
@@ -13,6 +13,7 @@ export type AuthProviderId =
13
13
  | "baseten"
14
14
  | "bedrock-mantle"
15
15
  | "cerebras"
16
+ | "charm-hyper"
16
17
  | "cline-pass"
17
18
  | "cloudflare-ai-gateway"
18
19
  | "commandcode"
@@ -94,6 +95,7 @@ export type LoginProviderId =
94
95
  | "anthropic"
95
96
  | "baseten"
96
97
  | "cerebras"
98
+ | "charm-hyper"
97
99
  | "cline-pass"
98
100
  | "cloudflare-ai-gateway"
99
101
  | "commandcode"
@@ -5,7 +5,7 @@ login-order "openai-codex" "anthropic" "zai" "zai-coding-plan" "kimi-code" "open
5
5
  "cursor" "devin" "google-antigravity" "google-gemini-cli" "openai-codex-device" "xai" "xai-oauth" \
6
6
  "gitlab-duo" "gitlab-duo-agent" "alibaba-coding-plan" "alibaba-token-plan" "aiand" "abliteration" "zhipu-coding-plan" \
7
7
  "umans" "qwen-portal" "sakana" "minimax-code" "minimax-code-cn" "xiaomi" "xiaomi-token-plan-sgp" \
8
- "xiaomi-token-plan-ams" "xiaomi-token-plan-cn" "firepass" "cline-pass" "commandcode" "deepseek" "muse-code" "meta" "moonshot" \
8
+ "xiaomi-token-plan-ams" "xiaomi-token-plan-cn" "firepass" "cline-pass" "commandcode" "charm-hyper" "deepseek" "muse-code" "meta" "moonshot" \
9
9
  "cerebras" "baseten" "fireworks" "together" "nvidia" "novita" "deepinfra" "huggingface" "perplexity" \
10
10
  "qianfan" "venice" "siliconflow" "siliconflow-cn" "synthetic" "nanogpt" "wafer-serverless" "coreweave" \
11
11
  "vercel-ai-gateway" "cloudflare-ai-gateway" "litellm" "kilo" "zenmux" "opencode-zen" "opencode-go" \
@@ -0,0 +1,19 @@
1
+ // Validation targets `/v1/credits`, not `/v1/models`: the models endpoint is
2
+ // public and answers 200 with no credentials or a bogus key (verified
3
+ // 2026-09-11), so a models-endpoint probe against it could never reject a bad
4
+ // key. `/v1/credits` is an authenticated, non-billable GET that returns the
5
+ // account balance on success and 401 `{"error":"authentication failed"}`
6
+ // otherwise — the same trick cline-pass uses to validate against its own
7
+ // `/users/me`, and cheaper than a chat-completions probe that would bill the
8
+ // key on every login.
9
+ auth "charm-hyper" {
10
+ name "Charm Hyper"
11
+ env "CHARM_HYPER_API_KEY" "HYPER_API_KEY"
12
+ login "api-key" {
13
+ auth-url "https://hyper.charm.land/"
14
+ instructions "Create or copy an API key from the Charm Hyper dashboard"
15
+ prompt "Paste your Charm Hyper API key" placeholder="sk-hyper-..."
16
+ normalize "strip-bearer"
17
+ validate "models-endpoint" label="Charm Hyper" url="https://hyper.charm.land/v1/credits"
18
+ }
19
+ }
@@ -28,8 +28,10 @@ class "minimax" {
28
28
  }
29
29
  // MiniMax-M3: 512K is the standard pricing-tier boundary leaked into
30
30
  // upstream metadata, not the model ceiling (documented: 1M in / 128K out).
31
- on "minimax" "minimax-cn" "minimax-code" "minimax-code-cn" {
32
- models "MiniMax-M3" {
31
+ // Exact selectors are case-sensitive, so both served spellings are listed:
32
+ // MiniMax hosts publish `MiniMax-M3`, Charm Hyper publishes `minimax-m3`.
33
+ on "charm-hyper" "minimax" "minimax-cn" "minimax-code" "minimax-code-cn" {
34
+ models "MiniMax-M3" "minimax-m3" {
33
35
  limits-patch {
34
36
  context-window 1000000
35
37
  max-tokens 128000
@@ -0,0 +1,82 @@
1
+ // Charm Hyper gateway deployment contract.
2
+ //
3
+ // Capability data is NOT restated here: `GET /v1/models` publishes each row's
4
+ // context window, output cap, vision flag, effort vocabulary and per-million
5
+ // tariff, and `charmHyperModelManagerOptions` maps all of it verbatim —
6
+ // including `reasoningDisableMode`, which is derived per model from whether
7
+ // the advertised vocabulary contains `none`. Only facts the gateway does not
8
+ // publish, or publishes wrongly, belong in this file.
9
+ provider "charm-hyper" {
10
+ // OpenAI-dialect gateway in front of open-weight and third-party models.
11
+ // `developer` and `store` are accepted (HTTP 200) but the gateway accepts
12
+ // every unknown field it is handed, so acceptance is not evidence the
13
+ // semantics are honored; both stay off and system prompts ride the
14
+ // `system` role, matching every sibling gateway. The wire takes
15
+ // `max_tokens` (verified 2026-09-11).
16
+ supports-developer-role #false
17
+ supports-store #false
18
+ max-tokens-field "max_tokens"
19
+ thinking-mode "effort"
20
+ // Reasoning arrives as `reasoning_content` on both the streamed delta and
21
+ // the final message (verified 2026-09-11).
22
+ reasoning-content-field "reasoning_content"
23
+ // ---------------------------------------------------------------------------
24
+ // Blockless rows are left non-reasoning on purpose.
25
+ //
26
+ // Four ids serve a thinking model while publishing no `reasoning` block:
27
+ // glm-5, glm-5.1, kimi-k2-thinking and minimax-m2.7 each returned
28
+ // populated `reasoning_content` and non-zero `reasoning_tokens` on a
29
+ // multi-step prompt (2026-09-11). The other blockless rows
30
+ // (gemma-4-26b-a4b-it, kimi-k2.5, kimi-k2.7-code, llama-3.3-70b-instruct,
31
+ // llama-4-maverick-17b-128e-instruct-fp8,
32
+ // qwen3-coder-480b-a35b-instruct-int4-mixed-ar, qwen3-next-80b-a3b-instruct)
33
+ // returned none.
34
+ //
35
+ // No rule restores them, because none can be written honestly. Those four
36
+ // expose no effort dial, and `ThinkingConfig.efforts` is documented as
37
+ // never empty — a reasoning model without a controllable surface carries
38
+ // `thinking: undefined`, which no KDL axis can express (the only upgrade
39
+ // path is an exact `thinking-efforts` ladder). Synthesizing a ladder would
40
+ // add silent no-op rungs: the gateway answers 200 to efforts it never
41
+ // advertised and simply ignores them, and a repeat measurement of effort
42
+ // sensitivity on these ids came back non-monotonic, i.e. sampling noise
43
+ // rather than a hidden dial.
44
+ //
45
+ // The cost is narrow and was checked: streamed `reasoning_content` is
46
+ // parsed and displayed with no `model.reasoning` gate, the effort
47
+ // parameter is moot without a dial, and the developer-role and
48
+ // zai-replay paths are already disabled for this provider.
49
+ // ---------------------------------------------------------------------------
50
+ // Residue: misreported output caps. The gateway derives
51
+ // `max_output_tokens` as a flat fraction of the context window rather
52
+ // than reporting a real ceiling — 10% on 9 rows, the dominant
53
+ // convention — and the values are advisory, not enforced: glm-5.1
54
+ // publishes 3276 yet produced 14066 tokens, and minimax-m2.7 publishes
55
+ // 6553 yet produced 8418, both stopping on `finish_reason: "stop"`
56
+ // rather than `length`. An over-stated cap is never rejected either
57
+ // (`max_tokens: 200000` returns 200), so the risk is one-sided:
58
+ // over-stating costs only the context share 10% already reserves
59
+ // everywhere else, while under-stating truncates real output.
60
+ //
61
+ // Both corrections take a same-window peer's published value rather than
62
+ // inventing a ceiling, and both clear the measured floor:
63
+ // glm-5.1 (202750) is 1.6%; sibling glm-5 (202752) publishes 20275.
64
+ // minimax-m2.7 (262100) is 2.5%, the lone outlier in its window class;
65
+ // kimi-k2.5 (262144), kimi-k2.6 (262000) and
66
+ // qwen3-next-80b (262144) all publish 26214.
67
+ //
68
+ // deepseek-v4.1-flash is the only other 2.5% row and is deliberately left
69
+ // alone: its 1M window has no peer group that agrees on a fraction, and a
70
+ // long generation stopped naturally at 8504, well inside the published
71
+ // 26214. Measured 2026-09-11.
72
+ models "glm-5.1" {
73
+ limits-patch {
74
+ max-tokens 20275
75
+ }
76
+ }
77
+ models "minimax-m2.7" {
78
+ limits-patch {
79
+ max-tokens 26214
80
+ }
81
+ }
82
+ }
@@ -82,11 +82,22 @@ provider "deepseek" {
82
82
  }
83
83
  }
84
84
  // residue: taxonomy ranks and exact globs do not isolate these models.
85
- models "deepseek-v4-flash" "deepseek-v4.1-flash-expires-on-0910" {
85
+ // The bare `deepseek-flash` alias serves DeepSeek-V4.1-Flash, so it shares
86
+ // the V4.1 wire contract: max_tokens + reasoning_content, mandatory replay
87
+ // (synthetic placeholders rejected upstream), no tool choice, and the
88
+ // low/high/max effort ladder the bare alias cannot inherit by family.
89
+ models "deepseek-flash" "deepseek-v4-flash" "deepseek-v4.1-flash-expires-on-0910" {
90
+ // Stale source capability data: discovery reports the bare alias as
91
+ // non-reasoning, so opt into the cascade upgrade for the exact ladder.
92
+ thinking-upgrade-neutral #true
86
93
  max-tokens-field "max_tokens"
87
94
  reasoning-content-field "reasoning_content"
88
95
  requires-assistant-content-for-tool-calls #true
89
96
  requires-reasoning-content-for-tool-calls #true
97
+ allows-synthetic-reasoning-content-for-tool-calls #false
90
98
  supports-tool-choice #false
99
+ // The bare alias carries no taxonomy family, so it cannot inherit the
100
+ // flash ladder; the exact rule owns it instead.
101
+ thinking-efforts "low" "high" "max"
91
102
  }
92
103
  }
@@ -13,6 +13,7 @@
13
13
  "auth/baseten.kdl",
14
14
  "auth/bedrock-mantle.kdl",
15
15
  "auth/cerebras.kdl",
16
+ "auth/charm-hyper.kdl",
16
17
  "auth/cline-pass.kdl",
17
18
  "auth/cloudflare-ai-gateway.kdl",
18
19
  "auth/commandcode.kdl",
@@ -114,6 +115,7 @@
114
115
  "providers/baseten.kdl",
115
116
  "providers/bedrock-mantle.kdl",
116
117
  "providers/cerebras.kdl",
118
+ "providers/charm-hyper.kdl",
117
119
  "providers/cline-pass.kdl",
118
120
  "providers/cloudflare-ai-gateway.kdl",
119
121
  "providers/commandcode.kdl",
@@ -5988,9 +5990,10 @@
5988
5990
  }
5989
5991
  },
5990
5992
  {
5991
- "source": "classes/minimax.kdl:32",
5993
+ "source": "classes/minimax.kdl:34",
5992
5994
  "class": "minimax",
5993
5995
  "providers": [
5996
+ "charm-hyper",
5994
5997
  "minimax",
5995
5998
  "minimax-cn",
5996
5999
  "minimax-code",
@@ -6000,6 +6003,10 @@
6000
6003
  {
6001
6004
  "kind": "exact",
6002
6005
  "value": "MiniMax-M3"
6006
+ },
6007
+ {
6008
+ "kind": "exact",
6009
+ "value": "minimax-m3"
6003
6010
  }
6004
6011
  ],
6005
6012
  "catalog": {
@@ -6010,7 +6017,7 @@
6010
6017
  }
6011
6018
  },
6012
6019
  {
6013
- "source": "classes/minimax.kdl:39",
6020
+ "source": "classes/minimax.kdl:41",
6014
6021
  "class": "minimax",
6015
6022
  "providers": [
6016
6023
  "alibaba-coding-plan",
@@ -6023,7 +6030,7 @@
6023
6030
  }
6024
6031
  },
6025
6032
  {
6026
- "source": "classes/minimax.kdl:44",
6033
+ "source": "classes/minimax.kdl:46",
6027
6034
  "class": "minimax",
6028
6035
  "providers": [
6029
6036
  "amazon-bedrock",
@@ -6040,7 +6047,7 @@
6040
6047
  }
6041
6048
  },
6042
6049
  {
6043
- "source": "classes/minimax.kdl:48",
6050
+ "source": "classes/minimax.kdl:50",
6044
6051
  "class": "minimax",
6045
6052
  "providers": [
6046
6053
  "kilo",
@@ -6058,7 +6065,7 @@
6058
6065
  }
6059
6066
  },
6060
6067
  {
6061
- "source": "classes/minimax.kdl:54",
6068
+ "source": "classes/minimax.kdl:56",
6062
6069
  "class": "minimax",
6063
6070
  "providers": [
6064
6071
  "minimax",
@@ -6075,7 +6082,7 @@
6075
6082
  }
6076
6083
  },
6077
6084
  {
6078
- "source": "classes/minimax.kdl:62",
6085
+ "source": "classes/minimax.kdl:64",
6079
6086
  "class": "minimax",
6080
6087
  "providers": [
6081
6088
  "minimax",
@@ -6097,7 +6104,7 @@
6097
6104
  }
6098
6105
  },
6099
6106
  {
6100
- "source": "classes/minimax.kdl:52",
6107
+ "source": "classes/minimax.kdl:54",
6101
6108
  "class": "minimax",
6102
6109
  "providers": [
6103
6110
  "minimax",
@@ -6109,7 +6116,7 @@
6109
6116
  }
6110
6117
  },
6111
6118
  {
6112
- "source": "classes/minimax.kdl:72",
6119
+ "source": "classes/minimax.kdl:74",
6113
6120
  "class": "minimax",
6114
6121
  "providers": [
6115
6122
  "minimax-code",
@@ -6121,7 +6128,7 @@
6121
6128
  }
6122
6129
  },
6123
6130
  {
6124
- "source": "classes/minimax.kdl:76",
6131
+ "source": "classes/minimax.kdl:78",
6125
6132
  "class": "minimax",
6126
6133
  "providers": [
6127
6134
  "minimax-code",
@@ -7590,6 +7597,55 @@
7590
7597
  "mode": "effort"
7591
7598
  }
7592
7599
  },
7600
+ {
7601
+ "source": "providers/charm-hyper.kdl:72",
7602
+ "providers": [
7603
+ "charm-hyper"
7604
+ ],
7605
+ "models": [
7606
+ {
7607
+ "kind": "exact",
7608
+ "value": "glm-5.1"
7609
+ }
7610
+ ],
7611
+ "catalog": {
7612
+ "limitsPatch": {
7613
+ "maxTokens": 20275
7614
+ }
7615
+ }
7616
+ },
7617
+ {
7618
+ "source": "providers/charm-hyper.kdl:77",
7619
+ "providers": [
7620
+ "charm-hyper"
7621
+ ],
7622
+ "models": [
7623
+ {
7624
+ "kind": "exact",
7625
+ "value": "minimax-m2.7"
7626
+ }
7627
+ ],
7628
+ "catalog": {
7629
+ "limitsPatch": {
7630
+ "maxTokens": 26214
7631
+ }
7632
+ }
7633
+ },
7634
+ {
7635
+ "source": "providers/charm-hyper.kdl:9",
7636
+ "providers": [
7637
+ "charm-hyper"
7638
+ ],
7639
+ "wire": {
7640
+ "supportsDeveloperRole": false,
7641
+ "supportsStore": false,
7642
+ "maxTokensField": "max_tokens",
7643
+ "reasoningContentField": "reasoning_content"
7644
+ },
7645
+ "thinking": {
7646
+ "mode": "effort"
7647
+ }
7648
+ },
7593
7649
  {
7594
7650
  "source": "providers/cline-pass.kdl:3",
7595
7651
  "providers": [
@@ -9761,11 +9817,15 @@
9761
9817
  }
9762
9818
  },
9763
9819
  {
9764
- "source": "providers/deepseek.kdl:85",
9820
+ "source": "providers/deepseek.kdl:89",
9765
9821
  "providers": [
9766
9822
  "deepseek"
9767
9823
  ],
9768
9824
  "models": [
9825
+ {
9826
+ "kind": "exact",
9827
+ "value": "deepseek-flash"
9828
+ },
9769
9829
  {
9770
9830
  "kind": "exact",
9771
9831
  "value": "deepseek-v4-flash"
@@ -9780,7 +9840,16 @@
9780
9840
  "reasoningContentField": "reasoning_content",
9781
9841
  "requiresAssistantContentForToolCalls": true,
9782
9842
  "requiresReasoningContentForToolCalls": true,
9843
+ "allowsSyntheticReasoningContentForToolCalls": false,
9783
9844
  "supportsToolChoice": false
9845
+ },
9846
+ "thinking": {
9847
+ "upgradeNeutral": true,
9848
+ "efforts": [
9849
+ "low",
9850
+ "high",
9851
+ "max"
9852
+ ]
9784
9853
  }
9785
9854
  },
9786
9855
  {
@@ -18324,6 +18393,30 @@
18324
18393
  "placeholder": "user_..."
18325
18394
  }
18326
18395
  },
18396
+ {
18397
+ "id": "charm-hyper",
18398
+ "apiKeyFormat": "bearer",
18399
+ "name": "Charm Hyper",
18400
+ "env": {
18401
+ "vars": [
18402
+ "CHARM_HYPER_API_KEY",
18403
+ "HYPER_API_KEY"
18404
+ ]
18405
+ },
18406
+ "login": {
18407
+ "kind": "api-key",
18408
+ "authUrl": "https://hyper.charm.land/",
18409
+ "instructions": "Create or copy an API key from the Charm Hyper dashboard",
18410
+ "prompt": "Paste your Charm Hyper API key",
18411
+ "placeholder": "sk-hyper-...",
18412
+ "normalize": "strip-bearer",
18413
+ "validate": {
18414
+ "kind": "models-endpoint",
18415
+ "label": "Charm Hyper",
18416
+ "url": "https://hyper.charm.land/v1/credits"
18417
+ }
18418
+ }
18419
+ },
18327
18420
  {
18328
18421
  "id": "deepseek",
18329
18422
  "apiKeyFormat": "bearer",
package/src/models.json CHANGED
@@ -76097,7 +76097,7 @@
76097
76097
  "api": "openai-completions",
76098
76098
  "provider": "deepseek",
76099
76099
  "baseUrl": "https://api.deepseek.com",
76100
- "reasoning": false,
76100
+ "reasoning": true,
76101
76101
  "input": [
76102
76102
  "text"
76103
76103
  ],
@@ -76153,8 +76153,8 @@
76153
76153
  "supportsUsageInStreaming": true,
76154
76154
  "alwaysSendMaxTokens": false,
76155
76155
  "disableReasoningOnForcedToolChoice": false,
76156
- "disableReasoningOnToolChoice": false,
76157
- "supportsToolChoice": true,
76156
+ "disableReasoningOnToolChoice": true,
76157
+ "supportsToolChoice": false,
76158
76158
  "supportsForcedToolChoice": true,
76159
76159
  "supportsNamedToolChoice": true,
76160
76160
  "maxTokensField": "max_tokens",
@@ -76163,28 +76163,23 @@
76163
76163
  "requiresThinkingAsText": false,
76164
76164
  "requiresMistralToolIds": false,
76165
76165
  "thinkingFormat": "openai",
76166
- "reasoningDisableMode": "lowest-effort",
76166
+ "reasoningDisableMode": "zai-thinking-disabled",
76167
76167
  "omitReasoningEffort": false,
76168
76168
  "includeEncryptedReasoning": true,
76169
76169
  "filterReasoningHistory": false,
76170
76170
  "reasoningContentField": "reasoning_content",
76171
- "requiresReasoningContentForToolCalls": false,
76172
- "requiresReasoningContentForAllAssistantTurns": false,
76173
- "allowsSyntheticReasoningContentForToolCalls": true,
76171
+ "requiresReasoningContentForToolCalls": true,
76172
+ "requiresReasoningContentForAllAssistantTurns": true,
76173
+ "allowsSyntheticReasoningContentForToolCalls": false,
76174
76174
  "replayReasoningContent": false,
76175
76175
  "qwenPreserveThinking": false,
76176
76176
  "qwenTemplateReasoningEffort": false,
76177
- "requiresAssistantContentForToolCalls": false,
76177
+ "requiresAssistantContentForToolCalls": true,
76178
76178
  "supportsPromptCacheBreakpoints": false,
76179
76179
  "isOpenRouterHost": false,
76180
76180
  "wireModelIdMode": "raw",
76181
76181
  "isVercelGatewayHost": false,
76182
76182
  "supportsStrictMode": true,
76183
- "extraBody": {
76184
- "thinking": {
76185
- "type": "enabled"
76186
- }
76187
- },
76188
76183
  "toolStrictMode": "mixed",
76189
76184
  "stripDeepseekSpecialTokens": true,
76190
76185
  "streamMarkupHealingPattern": "dsml",
@@ -76199,9 +76194,79 @@
76199
76194
  "thinkingLoopGuard": "deepseek",
76200
76195
  "rejectRootObjectUnion": false,
76201
76196
  "retryWithoutStrictOnGrammarError": false,
76202
- "supportsPromptCacheKey": false
76197
+ "supportsPromptCacheKey": false,
76198
+ "streamIdleTimeoutMs": 300000,
76199
+ "whenThinking": {
76200
+ "supportsStore": false,
76201
+ "supportsDeveloperRole": false,
76202
+ "supportsMultipleSystemMessages": true,
76203
+ "supportsReasoningEffort": true,
76204
+ "supportsReasoningParams": true,
76205
+ "supportsSamplingParams": true,
76206
+ "supportsPenaltyAndStopParams": true,
76207
+ "reasoningEffortMap": {},
76208
+ "supportsUsageInStreaming": true,
76209
+ "alwaysSendMaxTokens": false,
76210
+ "disableReasoningOnForcedToolChoice": false,
76211
+ "disableReasoningOnToolChoice": true,
76212
+ "supportsToolChoice": false,
76213
+ "supportsForcedToolChoice": true,
76214
+ "supportsNamedToolChoice": true,
76215
+ "maxTokensField": "max_tokens",
76216
+ "requiresToolResultName": false,
76217
+ "requiresAssistantAfterToolResult": false,
76218
+ "requiresThinkingAsText": false,
76219
+ "requiresMistralToolIds": false,
76220
+ "thinkingFormat": "openai",
76221
+ "reasoningDisableMode": "lowest-effort",
76222
+ "omitReasoningEffort": false,
76223
+ "includeEncryptedReasoning": true,
76224
+ "filterReasoningHistory": false,
76225
+ "reasoningContentField": "reasoning_content",
76226
+ "requiresReasoningContentForToolCalls": true,
76227
+ "requiresReasoningContentForAllAssistantTurns": true,
76228
+ "allowsSyntheticReasoningContentForToolCalls": false,
76229
+ "replayReasoningContent": false,
76230
+ "qwenPreserveThinking": false,
76231
+ "qwenTemplateReasoningEffort": false,
76232
+ "requiresAssistantContentForToolCalls": true,
76233
+ "supportsPromptCacheBreakpoints": false,
76234
+ "isOpenRouterHost": false,
76235
+ "wireModelIdMode": "raw",
76236
+ "isVercelGatewayHost": false,
76237
+ "supportsStrictMode": true,
76238
+ "extraBody": {
76239
+ "thinking": {
76240
+ "type": "enabled"
76241
+ }
76242
+ },
76243
+ "toolStrictMode": "mixed",
76244
+ "streamIdleTimeoutMs": 300000,
76245
+ "stripDeepseekSpecialTokens": true,
76246
+ "streamMarkupHealingPattern": "dsml",
76247
+ "reasoningDeltasMayBeCumulative": false,
76248
+ "emptyLengthFinishIsContextError": false,
76249
+ "usesOpenAIToolCallIdLimit": false,
76250
+ "dropThinkingWhenReasoningEffort": false,
76251
+ "nativeKimiK3Reasoning": false,
76252
+ "zaiReasoningEffortDialect": false,
76253
+ "clampOutputToModelMax": true,
76254
+ "stripImageInput": true,
76255
+ "thinkingLoopGuard": "deepseek",
76256
+ "rejectRootObjectUnion": false,
76257
+ "retryWithoutStrictOnGrammarError": false,
76258
+ "supportsPromptCacheKey": false
76259
+ }
76203
76260
  },
76204
- "supportsComputerUse": false
76261
+ "supportsComputerUse": false,
76262
+ "thinking": {
76263
+ "mode": "effort",
76264
+ "efforts": [
76265
+ "low",
76266
+ "high",
76267
+ "max"
76268
+ ]
76269
+ }
76205
76270
  },
76206
76271
  "deepseek-v4-flash": {
76207
76272
  "id": "deepseek-v4-flash",
@@ -1,3 +1,4 @@
1
+ import { CHARM_HYPER_API_BASE_URL, normalizeCharmHyperBaseUrl } from "../wire/charm-hyper";
1
2
  import { PERSONAL_GITHUB_COPILOT_BASE_URL } from "../wire/github-copilot";
2
3
 
3
4
  export interface ModelCacheProviderIdOptions {
@@ -19,6 +20,8 @@ export function isCredentialScopedModelCacheProvider(providerId: string): boolea
19
20
 
20
21
  export function getDefaultModelDiscoveryBaseUrl(providerId: string): string | undefined {
21
22
  switch (providerId) {
23
+ case "charm-hyper":
24
+ return CHARM_HYPER_API_BASE_URL;
22
25
  case "meta":
23
26
  case "muse-code":
24
27
  return "https://api.meta.ai/v1";
@@ -62,6 +65,23 @@ export function resolveModelCacheProviderId(providerId: string, options: ModelCa
62
65
  // carry `requestModelId: *-low`, which the Start plan refuses; refetch
63
66
  // so the collapsed default is re-pointed to `-medium` (issue #9478).
64
67
  return "cursor:default-effort-v4";
68
+ case "charm-hyper": {
69
+ // Discovery is authoritative for this gateway, so a warm cache is served
70
+ // for its full TTL without re-probing: the namespace must follow the
71
+ // configured endpoint, or a self-hosted proxy keeps serving the canonical
72
+ // host's roster, capabilities and tariffs until expiry.
73
+ //
74
+ // Endpoint-only scope is deliberate. `/v1/models` is public here, so the
75
+ // roster does not vary by key, and `charm-hyper` is absent from
76
+ // CREDENTIAL_SCOPED_MODEL_CACHE_PROVIDERS — `ModelRegistry` resolves this
77
+ // namespace with no credential at all, so hashing one would split it
78
+ // against the namespace discovery computes and miss forever.
79
+ //
80
+ // Normalized through the shared helper because the registry passes the
81
+ // raw configured value while `charmHyperModelManagerOptions` passes a
82
+ // `/v1`-suffixed one; both must land on one namespace.
83
+ return `charm-hyper:models-v1:${Bun.hash(normalizeCharmHyperBaseUrl(options.baseUrl)).toString(36)}`;
84
+ }
65
85
  case "muse-code": {
66
86
  const baseUrl = options.baseUrl ?? getDefaultModelDiscoveryBaseUrl(providerId)!;
67
87
  const scope = `${options.apiKey ?? ""}\u0000${baseUrl}`;
@@ -18,6 +18,7 @@ import {
18
18
  basetenModelManagerOptions,
19
19
  bedrockMantleModelManagerOptions,
20
20
  cerebrasModelManagerOptions,
21
+ charmHyperModelManagerOptions,
21
22
  clinePassModelManagerOptions,
22
23
  cloudflareAiGatewayModelManagerOptions,
23
24
  commandCodeModelManagerOptions,
@@ -150,6 +151,26 @@ export const CATALOG_PROVIDERS = [
150
151
  createModelManagerOptions: (config: ModelManagerConfig) => cerebrasModelManagerOptions(config),
151
152
  catalogDiscovery: { label: "Cerebras" },
152
153
  },
154
+ {
155
+ id: "charm-hyper",
156
+ defaultModel: "glm-5.3",
157
+ envVars: ["CHARM_HYPER_API_KEY", "HYPER_API_KEY"],
158
+ createModelManagerOptions: (config: ModelManagerConfig) => charmHyperModelManagerOptions(config),
159
+ allowUnauthenticated: true,
160
+ dynamicModelsAuthoritative: true,
161
+ // The gateway row is the whole truth for a Hyper deployment. Same-id rows
162
+ // on other hosts disagree with it in both directions (it serves
163
+ // non-thinking Kimi K2.5/K2.7-Code and a text-only Gemma 4 that their
164
+ // upstream homes list as reasoning/vision), so foreign backfills would
165
+ // advertise capabilities this deployment does not have.
166
+ skipCrossProviderReferenceFills: true,
167
+ // Deliberately NO `catalogDiscovery`: that field is what enrolls a provider
168
+ // in generate-models.ts. This gateway's catalog is live deployment truth,
169
+ // so generating would freeze one hyper.charm.land snapshot into
170
+ // models.json — and since discovery here needs no credentials, it would
171
+ // happen on every regen, contradicting the runtime-only contract
172
+ // compat-conformance.test.ts pins for this provider.
173
+ },
153
174
  {
154
175
  id: "cloudflare-ai-gateway",
155
176
  defaultModel: "anthropic/claude-opus-4-8",
@@ -28,6 +28,7 @@ import { type GeneratedProvider, getBundledModels } from "../models";
28
28
  import type { Api, FetchImpl, Model, ModelSpec, OpenAICompat, Provider, ThinkingConfig, TokenCost } from "../types";
29
29
  import { discoveryFetch, isAnthropicOAuthToken, isRecord, toBoolean, toNumber, toPositiveNumber } from "../utils";
30
30
  import { ALIBABA_TOKEN_PLAN_BASE_URL, parseAlibabaTokenPlanCredential } from "../wire/alibaba-token-plan";
31
+ import { normalizeCharmHyperBaseUrl } from "../wire/charm-hyper";
31
32
  import { CLINEPASS_API_BASE_URL, clinePassClientHeaders } from "../wire/cline-pass";
32
33
  import { CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL } from "../wire/cloudflare-ai-gateway";
33
34
  import { coreWeaveProjectHeaders } from "../wire/coreweave";
@@ -7386,3 +7387,149 @@ export function commandCodeModelManagerOptions(config?: CommandCodeModelManagerC
7386
7387
  },
7387
7388
  };
7388
7389
  }
7390
+
7391
+ // ---------------------------------------------------------------------------
7392
+ // Charm Hyper
7393
+ // ---------------------------------------------------------------------------
7394
+
7395
+ /**
7396
+ * Configuration for the Charm Hyper model manager.
7397
+ *
7398
+ * `baseUrl` overrides the gateway root for tests and self-hosted proxies; a
7399
+ * value that omits the `/v1` surface gains one, so a host-only override
7400
+ * behaves like every sibling provider's.
7401
+ */
7402
+ export interface CharmHyperModelManagerConfig {
7403
+ apiKey?: string;
7404
+ baseUrl?: string;
7405
+ fetch?: FetchImpl;
7406
+ }
7407
+
7408
+ /**
7409
+ * Charm Hyper's `/v1/models` row shape, verified live against
7410
+ * hyper.charm.land (2026-09-11). The gateway is unusually complete: every row
7411
+ * carries its own display name, context window, output cap, vision flag,
7412
+ * accepted `reasoning_effort` vocabulary and per-million-token tariff, so
7413
+ * discovery reads capabilities straight off the wire instead of borrowing a
7414
+ * bundled reference from another host. Rows are served publicly — the
7415
+ * endpoint answers 200 with no credentials at all.
7416
+ */
7417
+ interface CharmHyperModelRecord extends OpenAICompatibleModelRecord {
7418
+ display_name?: unknown;
7419
+ context_window?: unknown;
7420
+ max_output_tokens?: unknown;
7421
+ capabilities?: unknown;
7422
+ reasoning?: unknown;
7423
+ pricing?: unknown;
7424
+ }
7425
+
7426
+ /**
7427
+ * Hyper's thinking-off wire tier. It is a disable state, not a rung: sending
7428
+ * `reasoning_effort: "none"` suppresses reasoning outright (verified — zero
7429
+ * reasoning tokens), while `minimal` still thinks. Advertising models
7430
+ * therefore keep their whole ladder and route the off switch through
7431
+ * `reasoningDisableMode`, the same split first-party GPT-5.6 uses. Efforts the
7432
+ * gateway does not advertise are accepted and silently ignored rather than
7433
+ * rejected, so the advertised vocabulary is the only reliable ladder.
7434
+ */
7435
+ const CHARM_HYPER_WIRE_EFFORT_NONE = "none";
7436
+
7437
+ /** Distinct `reasoning.effort_levels[].value` strings advertised for a row. */
7438
+ function charmHyperWireEfforts(reasoning: unknown): readonly string[] {
7439
+ if (!isRecord(reasoning) || !Array.isArray(reasoning.effort_levels)) return [];
7440
+ const values: string[] = [];
7441
+ for (const level of reasoning.effort_levels) {
7442
+ const value = isRecord(level) ? level.value : undefined;
7443
+ if (typeof value === "string" && !values.includes(value)) values.push(value);
7444
+ }
7445
+ return values;
7446
+ }
7447
+
7448
+ /**
7449
+ * Build the effort ladder from the advertised vocabulary, preserving every
7450
+ * named tier. `none` is deliberately excluded: it is the disable state, and
7451
+ * folding it into `minimal` would make the lowest rung silently stop thinking
7452
+ * on the models that advertise both.
7453
+ */
7454
+ function resolveCharmHyperThinking(reasoning: unknown, wireEfforts: readonly string[]): ThinkingConfig | undefined {
7455
+ const efforts = THINKING_EFFORTS.filter(effort => wireEfforts.includes(effort));
7456
+ if (efforts.length === 0) return undefined;
7457
+ const advertisedDefault = isRecord(reasoning) ? reasoning.default_effort_level : undefined;
7458
+ const defaultLevel = efforts.find(effort => effort === advertisedDefault);
7459
+ return { mode: "effort", efforts, ...(defaultLevel !== undefined && { defaultLevel }) };
7460
+ }
7461
+
7462
+ /**
7463
+ * Hyper quotes per-million-token USD directly, so rates pass through
7464
+ * unscaled. `toPositiveNumber` is unusable here: a free tier and the common
7465
+ * `cache_create: 0` are legitimate zero rates, not missing values.
7466
+ */
7467
+ function toCharmHyperRate(value: unknown): number {
7468
+ const parsed = toNumber(value);
7469
+ return parsed !== undefined && parsed >= 0 ? parsed : 0;
7470
+ }
7471
+
7472
+ function resolveCharmHyperCost(pricing: unknown): ModelSpec<"openai-completions">["cost"] {
7473
+ if (!isRecord(pricing)) return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
7474
+ return {
7475
+ input: toCharmHyperRate(pricing.input),
7476
+ output: toCharmHyperRate(pricing.output),
7477
+ cacheRead: toCharmHyperRate(pricing.cache_hit),
7478
+ cacheWrite: toCharmHyperRate(pricing.cache_create),
7479
+ };
7480
+ }
7481
+
7482
+ /**
7483
+ * Charm Hyper's gateway catalog. `/v1/models` is public and carries the live
7484
+ * tariff, so discovery runs with or without a key and the snapshot is
7485
+ * authoritative: a model the gateway stops serving is pruned rather than kept
7486
+ * alive by a stale bundled row.
7487
+ */
7488
+ export function charmHyperModelManagerOptions(
7489
+ config?: CharmHyperModelManagerConfig,
7490
+ ): ModelManagerOptions<"openai-completions"> {
7491
+ const baseUrl = normalizeCharmHyperBaseUrl(config?.baseUrl);
7492
+ return {
7493
+ providerId: "charm-hyper",
7494
+ cacheProviderId: resolveModelCacheProviderId("charm-hyper", { baseUrl }),
7495
+ dynamicModelsAuthoritative: true,
7496
+ fetchDynamicModels: () =>
7497
+ fetchOpenAICompatibleModels({
7498
+ api: "openai-completions",
7499
+ provider: "charm-hyper",
7500
+ baseUrl,
7501
+ apiKey: config?.apiKey,
7502
+ mapModel: (
7503
+ entry: OpenAICompatibleModelRecord,
7504
+ defaults: ModelSpec<"openai-completions">,
7505
+ ): ModelSpec<"openai-completions"> => {
7506
+ const record = entry as CharmHyperModelRecord;
7507
+ const wireEfforts = charmHyperWireEfforts(record.reasoning);
7508
+ const thinking = resolveCharmHyperThinking(record.reasoning, wireEfforts);
7509
+ const capabilities = isRecord(record.capabilities) ? record.capabilities : undefined;
7510
+ return {
7511
+ ...defaults,
7512
+ name: toModelName(record.display_name, defaults.name),
7513
+ // A row without an `effort_levels` vocabulary exposes no dial.
7514
+ // The gateway is silent rather than negative about always-on
7515
+ // reasoners, so the handful that think anyway are corrected by
7516
+ // exact `thinking-efforts` rules in KDL, which upgrade the
7517
+ // target and materialize `reasoning: true` alongside them.
7518
+ reasoning: thinking !== undefined,
7519
+ ...(thinking && { thinking }),
7520
+ input: capabilities?.vision === true ? ["text", "image"] : ["text"],
7521
+ contextWindow: toPositiveNumber(record.context_window, defaults.contextWindow),
7522
+ maxTokens: toPositiveNumber(record.max_output_tokens, defaults.maxTokens),
7523
+ cost: resolveCharmHyperCost(record.pricing),
7524
+ // Thinking-capable rows that advertise the `none` tier can be
7525
+ // switched off on the wire; the rest have no off switch and
7526
+ // keep the dialect default.
7527
+ ...(thinking && wireEfforts.includes(CHARM_HYPER_WIRE_EFFORT_NONE)
7528
+ ? { compat: { reasoningDisableMode: "none-effort" as const } }
7529
+ : {}),
7530
+ };
7531
+ },
7532
+ fetch: config?.fetch,
7533
+ }),
7534
+ };
7535
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Charm Hyper gateway endpoint, shared so a host migration — or a self-hosted
3
+ * proxy override — touches a single module.
4
+ */
5
+ export const CHARM_HYPER_API_BASE_URL = "https://hyper.charm.land/v1";
6
+
7
+ /**
8
+ * Resolve a configured Charm Hyper base URL onto the gateway's `/v1` surface.
9
+ *
10
+ * Every consumer must agree on this, because they key different things off the
11
+ * result: inference and discovery target it, `/usage` sends a bearer token to
12
+ * it, and the model cache namespace is hashed from it. Three separate copies
13
+ * previously disagreed for a blank value — the model manager treated it as
14
+ * absent and used the canonical host, while the others produced a bare `/v1` —
15
+ * so a whitespace-only override silently split inference, balance checks and
16
+ * caching across different endpoints.
17
+ *
18
+ * A blank or whitespace-only value therefore means "not configured" and
19
+ * resolves to the canonical host; anything else keeps its host and gains the
20
+ * `/v1` segment if it omits one.
21
+ */
22
+ export function normalizeCharmHyperBaseUrl(baseUrl?: string): string {
23
+ const trimmed = baseUrl?.trim().replace(/\/+$/, "");
24
+ if (!trimmed) return CHARM_HYPER_API_BASE_URL;
25
+ return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
26
+ }