@bitkyc08/opencodex 2.42.0 → 2.43.0

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.
Files changed (246) hide show
  1. package/AGENTS_INSTALL.md +2 -2
  2. package/README.md +31 -0
  3. package/bin/ocx.mjs +10 -1
  4. package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
  5. package/gui/dist/assets/index-Djowl68T.js +112 -0
  6. package/gui/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/src/adapters/anthropic-image-codec.ts +304 -0
  9. package/src/adapters/anthropic-image-normalize.ts +8 -298
  10. package/src/adapters/anthropic.ts +30 -7
  11. package/src/adapters/command-code.ts +7 -5
  12. package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
  13. package/src/adapters/cursor/images.ts +36 -6
  14. package/src/adapters/cursor/live-transport.ts +7 -2
  15. package/src/adapters/cursor/native-exec-desktop.ts +2 -15
  16. package/src/adapters/cursor/protobuf-request.ts +13 -3
  17. package/src/adapters/cursor/tool-definitions.ts +5 -670
  18. package/src/adapters/cursor/tool-guidance.ts +236 -0
  19. package/src/adapters/cursor/tool-naming.ts +252 -0
  20. package/src/adapters/cursor/tool-schemas.ts +195 -0
  21. package/src/adapters/cursor/types.ts +6 -3
  22. package/src/adapters/exec-tool-result-normalize.ts +1 -1
  23. package/src/adapters/google-errors.ts +9 -1
  24. package/src/adapters/google.ts +1 -0
  25. package/src/adapters/kiro-calibration.ts +181 -0
  26. package/src/adapters/kiro.ts +135 -3
  27. package/src/adapters/openai-responses.ts +216 -26
  28. package/src/adapters/responses-code-mode.ts +59 -0
  29. package/src/adapters/tool-catalog-nudge.ts +1 -1
  30. package/src/adapters/xai-schema-analysis.ts +86 -0
  31. package/src/adapters/xai-tool-schema.ts +2 -87
  32. package/src/adapters/xai-web-search.ts +1 -1
  33. package/src/bridge.ts +22 -10
  34. package/src/chat/inbound.ts +11 -3
  35. package/src/claude/inbound-content-options.ts +60 -0
  36. package/src/claude/inbound-model-options.ts +142 -0
  37. package/src/claude/inbound-records.ts +7 -0
  38. package/src/claude/inbound.ts +10 -202
  39. package/src/claude/model-info.ts +45 -0
  40. package/src/cli/account-auth.ts +21 -6
  41. package/src/cli/capabilities.ts +13 -2
  42. package/src/cli/claude.ts +232 -39
  43. package/src/cli/config-command.ts +9 -1
  44. package/src/cli/dispatch.ts +5 -1
  45. package/src/cli/doctor.ts +10 -0
  46. package/src/cli/effort.ts +372 -0
  47. package/src/cli/export-command.ts +3 -9
  48. package/src/cli/help.ts +1 -0
  49. package/src/cli/index.ts +13 -0
  50. package/src/cli/init.ts +4 -0
  51. package/src/cli/model-selection-guidance.ts +30 -0
  52. package/src/cli/models-runtime.ts +3 -2
  53. package/src/cli/models.ts +8 -3
  54. package/src/cli/opencode.ts +4 -1
  55. package/src/cli/provider-runtime.ts +65 -0
  56. package/src/cli/provider.ts +8 -0
  57. package/src/cli/registry.ts +16 -2
  58. package/src/cli/runtime-api.ts +3 -1
  59. package/src/cli/star-prompt.ts +22 -6
  60. package/src/cli/status-probes.ts +168 -0
  61. package/src/cli/status.ts +5 -168
  62. package/src/clients/config-export/constants.ts +69 -0
  63. package/src/clients/config-export/contracts.ts +154 -0
  64. package/src/clients/config-export/dsh.ts +132 -0
  65. package/src/clients/config-export/fast-models.ts +29 -0
  66. package/src/clients/config-export/mcode.ts +83 -0
  67. package/src/clients/config-export/model-metadata.ts +108 -0
  68. package/src/clients/config-export/omp.ts +104 -0
  69. package/src/clients/config-export/zcode.ts +92 -0
  70. package/src/clients/config-export.ts +18 -710
  71. package/src/codex/account-lifecycle.ts +20 -3
  72. package/src/codex/account-usability.ts +2 -0
  73. package/src/codex/auth-api.ts +170 -23
  74. package/src/codex/auth-context.ts +328 -24
  75. package/src/codex/catalog/effort.ts +30 -4
  76. package/src/codex/catalog/metadata.ts +39 -9
  77. package/src/codex/catalog/native-models.ts +53 -12
  78. package/src/codex/catalog/parsing.ts +119 -5
  79. package/src/codex/catalog/provider-fetch.ts +150 -20
  80. package/src/codex/catalog/reserve.ts +52 -0
  81. package/src/codex/catalog/sync.ts +89 -16
  82. package/src/codex/convergence-types.ts +1 -0
  83. package/src/codex/convergence.ts +2 -0
  84. package/src/codex/data/upstream-models.json +1 -1
  85. package/src/codex/forward-transport-headers.ts +25 -0
  86. package/src/codex/inject.ts +3 -28
  87. package/src/codex/legacy-config-keys.ts +68 -0
  88. package/src/codex/log-guard/inspect-schema.ts +137 -0
  89. package/src/codex/log-guard/inspect.ts +2 -134
  90. package/src/codex/loopback-target.ts +54 -0
  91. package/src/codex/main-account-cache.ts +63 -1
  92. package/src/codex/main-account-hard-lock.ts +52 -0
  93. package/src/codex/main-account.ts +3 -1
  94. package/src/codex/management-convergence.ts +3 -0
  95. package/src/codex/model-entitlements.ts +54 -4
  96. package/src/codex/project-config-warnings.ts +92 -2
  97. package/src/codex/prompt-layers/encoding.ts +80 -0
  98. package/src/codex/prompt-layers/paths.ts +54 -0
  99. package/src/codex/prompt-layers/revision.ts +55 -0
  100. package/src/codex/prompt-layers/toml-edit.ts +163 -0
  101. package/src/codex/prompt-layers/toml-read.ts +181 -0
  102. package/src/codex/prompt-layers.ts +14 -520
  103. package/src/codex/quota-auto-refresh-state.ts +16 -0
  104. package/src/codex/quota-auto-refresh.ts +219 -0
  105. package/src/codex/quota-types.ts +51 -0
  106. package/src/codex/quota.ts +252 -93
  107. package/src/codex/reserve-availability.ts +177 -0
  108. package/src/codex/routing.ts +28 -9
  109. package/src/codex/shim.ts +53 -11
  110. package/src/codex/subagent-model-fallback.ts +23 -3
  111. package/src/combos/failover.ts +125 -7
  112. package/src/combos/identifiers.ts +89 -0
  113. package/src/combos/index.ts +4 -0
  114. package/src/combos/resolve.ts +80 -9
  115. package/src/combos/types.ts +20 -93
  116. package/src/config/subagent-models.ts +24 -0
  117. package/src/config.ts +156 -13
  118. package/src/generated/compatibility-version.json +454 -166
  119. package/src/generated/model-metadata.ts +1 -1
  120. package/src/integrations/journal.ts +65 -4
  121. package/src/integrations/store.ts +5 -0
  122. package/src/lab/events/limits.ts +4 -0
  123. package/src/lib/destination-policy.ts +31 -2
  124. package/src/lib/errors.ts +39 -0
  125. package/src/lib/provider-outbound.ts +69 -3
  126. package/src/lib/proxy-env.ts +22 -0
  127. package/src/lib/redact-folding.ts +176 -0
  128. package/src/lib/redact.ts +2 -175
  129. package/src/lib/state-store-sweeper.ts +20 -6
  130. package/src/lib/token-estimate.ts +94 -27
  131. package/src/lib/windows-user-principal.ts +53 -5
  132. package/src/oauth/anthropic-routing.ts +99 -3
  133. package/src/oauth/generic-account-failover.ts +36 -13
  134. package/src/oauth/index.ts +127 -26
  135. package/src/oauth/login-cli.ts +5 -0
  136. package/src/oauth/meta-muse.ts +117 -15
  137. package/src/oauth/pool-settings-capability.ts +15 -4
  138. package/src/providers/api-keys.ts +8 -10
  139. package/src/providers/default-aliases.ts +39 -0
  140. package/src/providers/derive.ts +10 -2
  141. package/src/providers/fastwire.ts +36 -7
  142. package/src/providers/initial-model-selection-runtime.ts +90 -0
  143. package/src/providers/initial-model-selection.ts +120 -0
  144. package/src/providers/key-failover.ts +134 -54
  145. package/src/providers/key-store.ts +11 -1
  146. package/src/providers/label.ts +1 -1
  147. package/src/providers/model-discovery.ts +76 -0
  148. package/src/providers/model-rename-startup.ts +72 -8
  149. package/src/providers/openai-sidecar.ts +17 -5
  150. package/src/providers/openai-tiers-destination.ts +102 -0
  151. package/src/providers/openai-tiers.ts +2 -99
  152. package/src/providers/opencode-go-transport.ts +41 -0
  153. package/src/providers/quota-key-accounts.ts +141 -0
  154. package/src/providers/quota-types.ts +9 -0
  155. package/src/providers/quota.ts +535 -104
  156. package/src/providers/registry.ts +60 -17
  157. package/src/providers/xai-responses-opt-in.ts +31 -5
  158. package/src/quota/reset-activation.ts +81 -0
  159. package/src/quota/reset-detector.ts +305 -0
  160. package/src/quota/reset-notify-config.ts +162 -0
  161. package/src/quota/reset-observer.ts +125 -0
  162. package/src/quota/reset-poller.ts +160 -0
  163. package/src/quota/reset-seen-store.ts +385 -0
  164. package/src/quota/reset-sinks.ts +199 -0
  165. package/src/quota/window-mapping.ts +106 -0
  166. package/src/responses/apply-patch-envelope.ts +46 -0
  167. package/src/responses/code-mode-helper-compat.ts +39 -1
  168. package/src/responses/custom-tool-compat.ts +10 -4
  169. package/src/responses/hosted-tool-policy.ts +12 -4
  170. package/src/responses/parser-content.ts +133 -0
  171. package/src/responses/parser-text-format.ts +24 -0
  172. package/src/responses/parser-tools.ts +188 -0
  173. package/src/responses/parser.ts +3 -326
  174. package/src/responses/state.ts +124 -28
  175. package/src/router.ts +48 -13
  176. package/src/routing/analytics.ts +1 -0
  177. package/src/routing/capability.ts +17 -4
  178. package/src/server/auth-cors.ts +7 -1
  179. package/src/server/background-lifecycle.ts +23 -1
  180. package/src/server/chat-completions.ts +25 -3
  181. package/src/server/claude-messages.ts +62 -5
  182. package/src/server/effort-row.ts +1 -1
  183. package/src/server/fast-row.ts +295 -0
  184. package/src/server/gui-static.ts +30 -4
  185. package/src/server/index.ts +107 -21
  186. package/src/server/management/agent-settings-routes.ts +2 -2
  187. package/src/server/management/combo-routes.ts +37 -9
  188. package/src/server/management/config-routes.ts +93 -2
  189. package/src/server/management/integration-routes.ts +108 -0
  190. package/src/server/management/model-routes.ts +13 -3
  191. package/src/server/management/model-rows.ts +20 -1
  192. package/src/server/management/native-integration-routes.ts +4 -1
  193. package/src/server/management/oauth-account-routes.ts +38 -10
  194. package/src/server/management/provider-routes.ts +34 -3
  195. package/src/server/management/quota-reset-routes.ts +57 -0
  196. package/src/server/management/route-registry.ts +7 -4
  197. package/src/server/management/shared.ts +19 -5
  198. package/src/server/management/system-routes.ts +3 -2
  199. package/src/server/management-api.ts +14 -2
  200. package/src/server/ports.ts +12 -2
  201. package/src/server/relay-eager.ts +38 -23
  202. package/src/server/relay.ts +4 -0
  203. package/src/server/request-log.ts +6 -0
  204. package/src/server/responses/codex-ws-correlation.ts +65 -0
  205. package/src/server/responses/codex-ws-exchange.ts +261 -0
  206. package/src/server/responses/codex-ws-metadata.ts +134 -0
  207. package/src/server/responses/codex-ws-pool.ts +162 -0
  208. package/src/server/responses/codex-ws-request.ts +87 -0
  209. package/src/server/responses/codex-ws-session.ts +93 -0
  210. package/src/server/responses/codex-ws-wire.ts +144 -0
  211. package/src/server/responses/collaboration.ts +41 -1
  212. package/src/server/responses/compact.ts +105 -12
  213. package/src/server/responses/core.ts +476 -57
  214. package/src/server/responses/fetch-helpers.ts +10 -3
  215. package/src/server/responses/input-admission.ts +16 -9
  216. package/src/server/responses/responses-field-backfill.ts +1 -1
  217. package/src/server/responses/ws-upstream.ts +34 -318
  218. package/src/server/responses-custom-tool-repair.ts +20 -4
  219. package/src/server/responses-undeclared-tool-guard.ts +100 -8
  220. package/src/server/safe-response-headers.ts +23 -0
  221. package/src/server/search.ts +9 -0
  222. package/src/server/subagent-models-startup.ts +27 -0
  223. package/src/server/system-env-shell.ts +238 -0
  224. package/src/server/system-env.ts +7 -234
  225. package/src/server/ws-bridge.ts +3 -25
  226. package/src/server/xai-responses-startup.ts +21 -0
  227. package/src/service-manager-probe.ts +1 -1
  228. package/src/service.ts +55 -16
  229. package/src/types/config.ts +108 -12
  230. package/src/types/provider.ts +36 -7
  231. package/src/types/tools.ts +26 -1
  232. package/src/types.ts +2 -0
  233. package/src/update/notify.ts +8 -2
  234. package/src/usage/cost.ts +38 -28
  235. package/src/usage/expected-prices.ts +34 -15
  236. package/src/usage/log.ts +2 -0
  237. package/src/usage/model-identity.ts +26 -0
  238. package/src/usage/summary.ts +15 -1
  239. package/src/vision/describe.ts +6 -0
  240. package/src/vision/image-rewrite.ts +108 -0
  241. package/src/vision/index.ts +19 -306
  242. package/src/vision/plan.ts +205 -0
  243. package/src/web-search/executor.ts +6 -0
  244. package/src/web-search/index.ts +8 -1
  245. package/gui/dist/assets/index-BU1tE0sr.js +0 -112
  246. package/gui/dist/assets/index-DL9-iS6J.css +0 -1
@@ -24,56 +24,29 @@ import { existsSync, readFileSync } from "node:fs";
24
24
  import { isAbsolute, join, resolve } from "node:path";
25
25
  import { shouldInjectApiAuthHeader, standaloneCodexRoutingTarget } from "../codex/inject";
26
26
  import { FORMAT_MEDIA_TYPE, serializeDocument, type ConfigFormat } from "../integrations/serialize";
27
- import { providerCodexAccountMode } from "../providers/registry";
28
- import { canonicalizeReasoningEfforts, sanitizeCodexReasoningEfforts } from "../reasoning-effort";
27
+ import { canonicalizeReasoningEfforts } from "../reasoning-effort";
28
+ import { expandFastExportModels } from "./config-export/fast-models";
29
29
  import { probeHostname } from "../server/proxy-liveness";
30
30
  import type { OcxConfig } from "../types";
31
31
 
32
- export type { ConfigFormat };
32
+ export type { ConfigFormat } from "../integrations/serialize";
33
+ export type { ManagedFragment, ManagedContribution, BuildContribution, OpencodeLaunchEnv, OpencodeCatalogModel, ExportModel, ExportContext, ExportClientId, ExportClientSpec, PiModelEntry } from "./config-export/contracts";
34
+ export { OPENCODE_PROVIDER_ID, OPENCODE_CONFIG_SCHEMA, OPENCODE_API_KEY_ENV, OPENCODE_API_KEY_ENV_REF, HERMES_API_KEY_ENV, HERMES_API_KEY_ENV_REF, OPENCLAW_API_KEY_ENV, OPENCLAW_API_KEY_ENV_REF, LOOPBACK_API_KEY_PLACEHOLDER, GAJAE_API_KEY_ENV, SCHEMA_REQUIRED_OUTPUT_BUDGET, OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG } from "./config-export/constants";
35
+ export { normalizeExportModels } from "./config-export/model-metadata";
36
+ export type { OmpModelEntry, OmpProviderBlock, OmpGeneratedConfig } from "./config-export/omp";
37
+ export type { ZcodeModelEntry, ZcodeProviderBlock, ZcodeGeneratedConfig } from "./config-export/zcode";
38
+ export type { DshReasoningEffort, DshWireReasoningEffort, DshModelEntry, DshProviderBlock, DshGeneratedConfig } from "./config-export/dsh";
39
+ export type { McodeProviderBlock, McodeModelEntry, McodeGeneratedConfig } from "./config-export/mcode";
33
40
 
34
- /**
35
- * One entry opencodex owns inside a client's config: the JSON path to it and
36
- * the value we put there.
37
- *
38
- * A path list rather than a single provider key because ownership is not
39
- * always one entry Kimi owns its provider block AND one model entry per
40
- * model, and a writer that only knew about the provider would strand the rest
41
- * (devlog 260802 006 §2).
42
- */
43
- export interface ManagedFragment {
44
- path: readonly string[];
45
- value: unknown;
46
- }
41
+ import type { OpencodeLaunchEnv, OpencodeCatalogModel, ExportContext, PiModelEntry, ManagedContribution, ManagedFragment, ExportClientId, ExportClientSpec } from "./config-export/contracts";
42
+ import { OPENCODE_API_KEY_ENV_REF, OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG, OPENCODE_CONFIG_SCHEMA, OPENCODE_PROVIDER_ID, PI_API_DIALECT, LOOPBACK_API_KEY_PLACEHOLDER, HERMES_API_KEY_ENV_REF, OPENCLAW_API_KEY_ENV_REF, GAJAE_API_KEY_ENV, OPENCODE_API_KEY_ENV, HERMES_API_KEY_ENV, OPENCLAW_API_KEY_ENV } from "./config-export/constants";
43
+ import { exportModelLabel, authoritativeContextWindow, outputBudgetFor, normalizeExportModels, inputModalitiesForClient, proxyAdmissionHeaders, singleFragment } from "./config-export/model-metadata";
44
+ import { buildOmpClientConfig, summarizeOmp, buildOmpContribution } from "./config-export/omp";
45
+ import { buildDshClientConfig, summarizeDsh, buildDshContribution } from "./config-export/dsh";
46
+ import { buildMcodeClientConfig, summarizeMcode, buildMcodeContribution } from "./config-export/mcode";
47
+ import { buildZcodeClientConfig, summarizeZcode, buildZcodeContribution } from "./config-export/zcode";
47
48
 
48
- /** Everything opencodex contributes to one client's config, as one unit. */
49
- export interface ManagedContribution {
50
- clientId: ExportClientId;
51
- fragments: readonly ManagedFragment[];
52
- }
53
-
54
- export type BuildContribution = (ctx: ExportContext) => ManagedContribution;
55
-
56
- export interface OpencodeLaunchEnv {
57
- [key: string]: string | undefined;
58
- }
59
49
 
60
- /** Visible catalog entry keyed by the proxy's canonical namespaced selector. */
61
- export interface OpencodeCatalogModel {
62
- namespaced: string;
63
- native?: boolean;
64
- provider?: string;
65
- id?: string;
66
- contextWindow?: number;
67
- displayName?: string;
68
- /** Declared effort ladder. Exported as opencode model variants where the client reads them. */
69
- reasoningEfforts?: readonly string[];
70
- /**
71
- * Declared default effort. Carried so every client export reads one deduped, visibility-
72
- * filtered ladder per model. The opencode serializer deliberately does NOT turn it into a
73
- * model-level setting — see {@link opencodeEffortVariants} for why.
74
- */
75
- defaultReasoningEffort?: string;
76
- }
77
50
 
78
51
  export interface OpencodeModelEntry {
79
52
  name: string;
@@ -137,11 +110,6 @@ export interface OpencodeGeneratedConfig {
137
110
  providers: Record<string, OpencodeV2ProviderBlock>;
138
111
  }
139
112
 
140
- /** Provider key owned by this project; the only key any exporter ever emits. */
141
- export const OPENCODE_PROVIDER_ID = "opencodex";
142
-
143
- export const OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
144
-
145
113
  /**
146
114
  * The proxy speaks the OpenAI-compatible shape at /v1, which opencode reaches through
147
115
  * the AI SDK's openai-compatible package (the same wiring users hand-write today).
@@ -163,67 +131,6 @@ const OPENCODE_V2_PROVIDER_PACKAGE = "@opencode-ai/ai/providers/openai-compatibl
163
131
  /** Display name for the provider block, identical in both generations. */
164
132
  const OPENCODE_PROVIDER_NAME = "OpenCodex";
165
133
 
166
- /**
167
- * Env var carrying the proxy admission key to opencode. The config only ever holds the
168
- * `{env:...}` reference, so the secret never lands on disk. opencode substitutes it at
169
- * load time.
170
- */
171
- export const OPENCODE_API_KEY_ENV = "OPENCODEX_OPENCODE_API_KEY";
172
-
173
- /** Env reference shared by apiKey and the dedicated proxy admission header. */
174
- export const OPENCODE_API_KEY_ENV_REF = `{env:${OPENCODE_API_KEY_ENV}}`;
175
-
176
- /**
177
- * Hermes interpolates `${VAR}` anywhere in config.yaml, so the credential stays
178
- * in the environment exactly as it does for OpenCode.
179
- */
180
- export const HERMES_API_KEY_ENV = "OPENCODEX_HERMES_API_KEY";
181
- export const HERMES_API_KEY_ENV_REF = `\${${HERMES_API_KEY_ENV}}`;
182
-
183
- /** OpenClaw interpolates `${UPPERCASE_VAR}` and fails closed when it is unset. */
184
- export const OPENCLAW_API_KEY_ENV = "OPENCODEX_OPENCLAW_API_KEY";
185
- export const OPENCLAW_API_KEY_ENV_REF = `\${${OPENCLAW_API_KEY_ENV}}`;
186
-
187
- /**
188
- * Placeholder credential for loopback-only clients (Kimi, Pi). A loopback
189
- * bind needs no real admission key, so we emit the same placeholder the Grok
190
- * managed block uses rather than a user secret. Pi resolves `apiKey` before
191
- * building its model list and hides the provider when an env reference is unset.
192
- */
193
- export const LOOPBACK_API_KEY_PLACEHOLDER = "opencodex-loopback";
194
-
195
- /**
196
- * Gajae's `apiKeyEnv` is env-name-only and fail-closed. Its sibling `apiKey`
197
- * falls back to treating the literal text as the token when the variable is
198
- * unset, which would silently ship a bogus credential — so we never emit it.
199
- */
200
- export const GAJAE_API_KEY_ENV = "OPENCODEX_GAJAE_API_KEY";
201
-
202
- /** Pi's wire-dialect selector for an OpenAI-compatible endpoint. */
203
- const PI_API_DIALECT = "openai-completions";
204
-
205
- /**
206
- * opencode's config schema rejects a `limit` block that carries `context` without
207
- * `output`, but CatalogModel has no authoritative per-model output field. Dropping
208
- * `limit` entirely would also throw away the authoritative context window we DO have,
209
- * so the block is emitted with this budget standing in for the missing half.
210
- *
211
- * The value matches REASONING_MAX_TOKENS_CEILING in src/adapters/anthropic.ts — the
212
- * project's existing "safe ceiling across current models" figure. It is a ceiling for
213
- * schema validity, NOT a claim about any specific model's true maximum, and it is
214
- * clamped to the context window so a small-context model can never be emitted with
215
- * output > context. Pi's `maxTokens` uses the same stand-in and the same clamp.
216
- */
217
- export const SCHEMA_REQUIRED_OUTPUT_BUDGET = 32_000;
218
-
219
- /** Deterministic loopback default for exported provider-block helpers in tests. */
220
- export const OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG: OcxConfig = {
221
- port: 10100,
222
- hostname: "127.0.0.1",
223
- defaultProvider: "mock",
224
- providers: { mock: { adapter: "openai-chat", baseUrl: "http://127.0.0.1/v1" } },
225
- } as OcxConfig;
226
-
227
134
  /**
228
135
  * Resolve the user's global opencode config path. opencode uses the XDG layout on every
229
136
  * platform (including Windows, where it is %USERPROFILE%\.config\opencode).
@@ -626,184 +533,6 @@ export function asideConfigPath(env: OpencodeLaunchEnv = process.env, home: stri
626
533
  return join(asideAccountDir(env, home), "models.json");
627
534
  }
628
535
 
629
- /**
630
- * One proxy-routed model destined for a client config. Deliberately narrower than
631
- * `CatalogModel` so a serializer cannot reach for a field that does not survive the
632
- * `/api/models` boundary.
633
- */
634
- export interface ExportModel {
635
- /** Canonical proxy selector: `provider/id`, or bare slug for native. */
636
- namespaced: string;
637
- provider: string;
638
- id: string;
639
- /** Native OpenAI entry. Read by the shared label rule. */
640
- native?: boolean;
641
- displayName?: string;
642
- contextWindow?: number;
643
- inputModalities?: string[];
644
- /** Optional effort ladder exported only to clients that support it. */
645
- reasoningEfforts?: string[];
646
- defaultReasoningEffort?: string;
647
- }
648
-
649
- export interface ExportContext {
650
- /** `http://host:port/v1` — the OpenAI-compatible surface the client dials. */
651
- baseUrl: string;
652
- models: readonly ExportModel[];
653
- /**
654
- * Live proxy config. Only the OpenCode path reads it: a non-loopback bind moves
655
- * admission from `apiKey` to the `x-opencodex-api-key` header.
656
- */
657
- config?: OcxConfig;
658
- }
659
-
660
- export type ExportClientId =
661
- | "opencode"
662
- | "pi"
663
- | "omp"
664
- | "hermes"
665
- | "openclaw"
666
- | "kimi"
667
- | "gajae"
668
- | "dsh"
669
- | "mcode"
670
- | "zcode"
671
- | "prime"
672
- | "aside";
673
-
674
- export interface ExportClientSpec {
675
- id: ExportClientId;
676
- /** Download filename; matches the destination file's own name (003 §5). */
677
- filename: string;
678
- /** Canonical destination for humans. Never written to. */
679
- destination: (env: NodeJS.ProcessEnv) => string;
680
- /** Env var the config references; the value is never serialized. */
681
- apiKeyEnv: string;
682
- /** Shell line the user runs before launching the client. */
683
- exportHint: string;
684
- build: (ctx: ExportContext) => unknown;
685
- /**
686
- * Text format of the client's config file. `filename` already carries the
687
- * extension; this drives serialization and the download media type so no
688
- * consumer has to infer either from the name.
689
- */
690
- format: ConfigFormat;
691
- /**
692
- * Count models in THIS client's document shape. Required so a new client
693
- * cannot be added without teaching the summarizer about it — the old
694
- * "anything that is not OpenCode must be Pi" branch was a latent bug.
695
- */
696
- summarize: (document: unknown) => { modelCount: number; modelsWithoutLimits: number };
697
- /**
698
- * The fragments opencodex owns inside this client's config. Only the builder
699
- * knows where a client keeps our entries, so ownership paths originate here
700
- * rather than being re-derived by the writer.
701
- */
702
- buildContribution: BuildContribution;
703
- /**
704
- * True when the generated integration deliberately supports loopback only.
705
- *
706
- * `/v1/chat/completions` rejects bearer credentials and requires the
707
- * dedicated `x-opencodex-api-key` header (AUTH_MATRIX in
708
- * src/server/auth-cors.ts). If this exporter cannot safely emit that header,
709
- * it refuses a remote bind rather than generating a config that 401s. Same
710
- * reasoning as the Grok managed block's non-loopback refusal.
711
- */
712
- loopbackOnly: boolean;
713
- }
714
-
715
- /**
716
- * Authoritative context window, or undefined. Never guesses: a missing, non-finite, or
717
- * non-positive value means the serializer omits every context-derived field.
718
- */
719
- function authoritativeContextWindow(contextWindow: number | undefined): number | undefined {
720
- if (typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0) {
721
- const integer = Math.floor(contextWindow);
722
- return integer > 0 ? integer : undefined;
723
- }
724
- return undefined;
725
- }
726
-
727
- /** Schema-required output budget for a known context window. */
728
- function outputBudgetFor(context: number): number {
729
- return Math.min(SCHEMA_REQUIRED_OUTPUT_BUDGET, context);
730
- }
731
-
732
- /**
733
- * Modalities a given client's schema will actually accept.
734
- *
735
- * Our internal vocabulary is `text | image | audio` (ALLOWED_INPUT_MODALITIES in
736
- * src/server/management/model-routes.ts). Pi and Gajae accept only
737
- * `text | image`, and both reject the WHOLE config file over one out-of-enum
738
- * value — Gajae reports `/providers/opencodex/models/N/input/2: Invalid option`
739
- * and falls back to its built-in list, Pi returns an empty model config. So a
740
- * single `audio` model takes every routed model down with it. That is not
741
- * hypothetical: zenmux/meta-muse-spark-1.1 advertises audio and did exactly
742
- * this. It is also the same defect the Codex catalog had with `video`, where
743
- * the app showed zero apps (tests/catalog-input-modality-enum.test.ts).
744
- *
745
- * UNKNOWN and INCOMPATIBLE are different inputs, and the Codex fix could
746
- * conflate them safely only because its enum is wider. A model with nothing
747
- * declared is unknown, and `text` is the honest floor — every routed model takes
748
- * prompts. A model declaring `["audio"]` and nothing else is incompatible with a
749
- * text|image client, and rewriting it to `["text"]` would advertise a capability
750
- * it does not have. That input is reachable three ways: `ocx models add
751
- * --modalities audio`, `/api/custom-models`, and provider discovery.
752
- *
753
- * So unknown falls back to text and incompatible returns null, which drops the
754
- * row. Omitting a model costs the user a line in a picker; fabricating `text`
755
- * costs them a model that fails at call time with no explanation.
756
- *
757
- * Deliberately NOT applied in `ExportModel` construction: the management and CLI
758
- * boundaries carry catalog modalities verbatim on purpose, and stripping `audio`
759
- * globally would destroy valid metadata before the destination is known.
760
- */
761
- const CLIENT_INPUT_MODALITIES: Record<"pi" | "gajae", ReadonlySet<string>> = {
762
- pi: new Set(["text", "image"]),
763
- gajae: new Set(["text", "image"]),
764
- };
765
-
766
- /** `null` means the model cannot be represented for this client — drop the row. */
767
- function inputModalitiesForClient(
768
- client: "pi" | "gajae",
769
- modalities: readonly string[] | undefined,
770
- ): string[] | null {
771
- const declared = modalities ?? [];
772
- if (declared.length === 0) return ["text"];
773
- const accepted = CLIENT_INPUT_MODALITIES[client];
774
- const kept: string[] = [];
775
- for (const value of declared) {
776
- if (accepted.has(value) && !kept.includes(value)) kept.push(value);
777
- }
778
- return kept.length > 0 ? kept : null;
779
- }
780
-
781
- /** DSH rc.6 accepts text/image; unknown values degrade to text, while audio-only cannot be represented. */
782
- function dshInputModalities(modalities: readonly string[] | undefined): string[] | null {
783
- const declared = modalities ?? [];
784
- if (declared.length === 0) return ["text"];
785
- const kept: string[] = [];
786
- for (const value of declared) {
787
- if ((value === "text" || value === "image") && !kept.includes(value)) kept.push(value);
788
- }
789
- if (kept.length > 0) return kept;
790
- return declared.every(value => value === "audio") ? null : ["text"];
791
- }
792
-
793
- /**
794
- * Label shared by every client: `"<displayName|id> (<native|provider|routed>)"`. The
795
- * provider suffix is what makes two same-named models from different upstreams
796
- * distinguishable in a client's model picker.
797
- */
798
- function exportModelLabel(model: OpencodeCatalogModel): string {
799
- const providerLabel = model.native ? "native" : (model.provider ?? "routed");
800
- const id = model.id ?? model.namespaced;
801
- if (model.displayName && model.displayName.length > 0) {
802
- return `${model.displayName} (${providerLabel})`;
803
- }
804
- return `${id} (${providerLabel})`;
805
- }
806
-
807
536
  /** Endpoint plus admission, identical for the V1 `options` and V2 `settings` field. */
808
537
  function opencodeProviderConnection(baseURL: string, config: OcxConfig): OpencodeProviderConnection {
809
538
  const options: OpencodeProviderConnection = { baseURL };
@@ -859,9 +588,8 @@ export function opencodeProviderBlocks(
859
588
  ): OpencodeProviderBlocks {
860
589
  const v1Models: Record<string, OpencodeModelEntry> = {};
861
590
  const v2Models: Record<string, OpencodeV2ModelEntry> = {};
862
- for (const model of catalogModels) {
591
+ for (const model of expandFastExportModels(catalogModels)) {
863
592
  const key = model.namespaced;
864
- if (v1Models[key]) continue; // first entry wins; native rows lead /api/models
865
593
  const entry: OpencodeModelEntry = { name: exportModelLabel(model) };
866
594
  const context = authoritativeContextWindow(model.contextWindow);
867
595
  if (context !== undefined) {
@@ -925,23 +653,6 @@ export function buildOpencodeProviderBlockFromCatalog(
925
653
  return opencodeProviderBlock(opencodeProxyBaseUrl(port, hostname), catalogModels, config);
926
654
  }
927
655
 
928
- /**
929
- * Shared precondition for every serializer: drop duplicate `namespaced` (first wins,
930
- * native rows lead `/api/models`) and sort by `namespaced` so two calls with the same
931
- * models produce identical bytes. Stability matters because the GUI shows a diffable
932
- * preview and agents may checksum the payload.
933
- */
934
- export function normalizeExportModels(models: readonly ExportModel[]): ExportModel[] {
935
- const seen = new Set<string>();
936
- const unique: ExportModel[] = [];
937
- for (const model of models) {
938
- if (seen.has(model.namespaced)) continue;
939
- seen.add(model.namespaced);
940
- unique.push(model);
941
- }
942
- return unique.sort((a, b) => (a.namespaced < b.namespaced ? -1 : a.namespaced > b.namespaced ? 1 : 0));
943
- }
944
-
945
656
  /**
946
657
  * OpenCode document: both provider generations plus `$schema`, and nothing else.
947
658
  *
@@ -961,23 +672,6 @@ function buildOpencodeClientConfig(ctx: ExportContext): OpencodeGeneratedConfig
961
672
  };
962
673
  }
963
674
 
964
- export interface PiModelEntry {
965
- id: string;
966
- name: string;
967
- input: string[];
968
- contextWindow?: number;
969
- maxTokens?: number;
970
- /** Advertised when the catalog row carries a non-empty effort ladder. */
971
- reasoning?: true;
972
- /**
973
- * Constrains pi's own level scale (minimal..max) to the declared ladder: members map to
974
- * themselves, everything else is hidden (`null`). Without it pi would offer levels the
975
- * ladder does not contain — harmless for provider-config ladders (the proxy clamps those
976
- * at the wire) but a real 400 risk for custom-row ladders, which are advertisement-only.
977
- */
978
- thinkingLevelMap?: Record<string, string | null>;
979
- }
980
-
981
675
  export interface PiProviderBlock {
982
676
  baseUrl: string;
983
677
  api: string;
@@ -989,50 +683,6 @@ export interface PiGeneratedConfig {
989
683
  providers: Record<string, PiProviderBlock>;
990
684
  }
991
685
 
992
- /**
993
- * omp accepts a model-level API override. Keep the provider on Chat
994
- * Completions so routed providers retain their established wire format, while
995
- * native OpenAI models can use the lossless Responses surface.
996
- */
997
- export interface OmpModelEntry extends PiModelEntry {
998
- api?: "openai-responses";
999
- /** omp requires this flag before it honors a thinking block. */
1000
- reasoning?: true;
1001
- thinking?: {
1002
- mode: "effort";
1003
- efforts: string[];
1004
- defaultLevel?: string;
1005
- };
1006
- }
1007
-
1008
- export interface OmpProviderBlock {
1009
- baseUrl: string;
1010
- api: typeof PI_API_DIALECT;
1011
- apiKey: string;
1012
- models: OmpModelEntry[];
1013
- }
1014
-
1015
- export interface OmpGeneratedConfig {
1016
- providers: Record<string, OmpProviderBlock>;
1017
- }
1018
-
1019
- /**
1020
- * omp validates model entries strictly. These are its documented effort
1021
- * values; omit an unknown value rather than invalidating the whole provider.
1022
- */
1023
- const OMP_EFFORT_VOCABULARY = new Set(["minimal", "low", "medium", "high", "xhigh", "max"]);
1024
-
1025
- function ompEfforts(model: ExportModel): string[] {
1026
- const efforts: string[] = [];
1027
- for (const effort of model.reasoningEfforts ?? []) {
1028
- const normalized = effort.trim().toLowerCase();
1029
- if (OMP_EFFORT_VOCABULARY.has(normalized) && !efforts.includes(normalized)) {
1030
- efforts.push(normalized);
1031
- }
1032
- }
1033
- return efforts;
1034
- }
1035
-
1036
686
  /**
1037
687
  * Hermes `~/.hermes/config.yaml`. We emit ONLY the provider entry — never
1038
688
  * `model.default` — because hijacking the user's main model is not what a
@@ -1124,85 +774,6 @@ export interface GajaeGeneratedConfig {
1124
774
  providers: Record<string, GajaeProviderBlock>;
1125
775
  }
1126
776
 
1127
- export type DshReasoningEffort = "low" | "medium" | "high" | "xhigh" | "max";
1128
- export type DshWireReasoningEffort = DshReasoningEffort | "ultra";
1129
-
1130
- export interface DshModelEntry {
1131
- id: string;
1132
- name: string;
1133
- input: string[];
1134
- contextWindow?: number;
1135
- reasoningEfforts?: Partial<Record<DshReasoningEffort, DshWireReasoningEffort>>;
1136
- }
1137
-
1138
- export interface DshProviderBlock {
1139
- displayName: "OpenCodex";
1140
- api: "openai-responses";
1141
- baseURL: string;
1142
- headers: { Authorization: "Bearer ocx_data_dsh" };
1143
- models: DshModelEntry[];
1144
- }
1145
-
1146
- export interface DshGeneratedConfig {
1147
- "llm-pi-ai": {
1148
- providers: Record<string, DshProviderBlock>;
1149
- };
1150
- }
1151
-
1152
- export interface McodeProviderBlock {
1153
- name: "OpenCodex";
1154
- kind: "custom";
1155
- enabled: true;
1156
- api: "anthropic-messages";
1157
- options: {
1158
- apiKey: string;
1159
- baseURL: string;
1160
- authMode: "api-key";
1161
- };
1162
- models: Record<string, McodeModelEntry>;
1163
- }
1164
-
1165
- export interface McodeModelEntry {
1166
- /** MCode uses this value for context accounting and compaction. */
1167
- limit?: { context: number };
1168
- /** MCode exposes these exact levels in `/model` and sends the selected effort. */
1169
- thinking?: { effortOptions: string[] };
1170
- }
1171
-
1172
- export interface McodeGeneratedConfig {
1173
- custom_provider: Record<string, McodeProviderBlock>;
1174
- }
1175
-
1176
- /**
1177
- * ZCode's `~/.zcode/v2/config.json` provider entry (observed schema, validated
1178
- * live against ZCode 3.7.7 / 3.8.1). `kind: "openai-compatible"` selects the
1179
- * OpenAI Chat Completions protocol, which the proxy serves at `/v1/chat/completions`.
1180
- * `apiKeyRequired` keeps ZCode's UI from prompting for a key it does not need on
1181
- * loopback; the serialized key is always the non-secret loopback placeholder.
1182
- */
1183
- export interface ZcodeModelEntry {
1184
- name?: string;
1185
- limit?: { context: number; output?: number };
1186
- modalities: { input: string[]; output: string[] };
1187
- }
1188
-
1189
- export interface ZcodeProviderBlock {
1190
- name: "OpenCodex";
1191
- kind: "openai-compatible";
1192
- enabled: true;
1193
- source: "custom";
1194
- options: {
1195
- apiKey: string;
1196
- baseURL: string;
1197
- apiKeyRequired: true;
1198
- };
1199
- models: Record<string, ZcodeModelEntry>;
1200
- }
1201
-
1202
- export interface ZcodeGeneratedConfig {
1203
- provider: Record<string, ZcodeProviderBlock>;
1204
- }
1205
-
1206
777
  /**
1207
778
  * Pi's `~/.pi/agent/models.json` shape. `models` is an ARRAY (identity lives in `id`),
1208
779
  * unlike OpenCode's keyed object.
@@ -1275,56 +846,6 @@ function buildPiClientConfig(ctx: ExportContext): PiGeneratedConfig {
1275
846
  };
1276
847
  }
1277
848
 
1278
- /**
1279
- * omp's models.yml is Pi-like, but it supports effort metadata and a per-model
1280
- * API dialect. Native OpenAI models use Responses; all routed models inherit
1281
- * the provider's existing Chat Completions dialect.
1282
- */
1283
- function buildOmpClientConfig(ctx: ExportContext): OmpGeneratedConfig {
1284
- const models: OmpModelEntry[] = [];
1285
- for (const model of normalizeExportModels(ctx.models)) {
1286
- const input = inputModalitiesForClient("pi", model.inputModalities);
1287
- if (input === null) continue;
1288
- const entry: OmpModelEntry = {
1289
- id: model.namespaced,
1290
- name: exportModelLabel(model),
1291
- input,
1292
- ...(model.native && model.provider === "openai" ? { api: "openai-responses" } : {}),
1293
- };
1294
- const context = authoritativeContextWindow(model.contextWindow);
1295
- if (context !== undefined) {
1296
- entry.contextWindow = context;
1297
- entry.maxTokens = outputBudgetFor(context);
1298
- }
1299
- const efforts = ompEfforts(model);
1300
- if (efforts.length > 0) {
1301
- const defaultLevel = model.defaultReasoningEffort?.trim().toLowerCase();
1302
- entry.reasoning = true;
1303
- entry.thinking = {
1304
- mode: "effort",
1305
- efforts,
1306
- ...(defaultLevel && efforts.includes(defaultLevel) ? { defaultLevel } : {}),
1307
- };
1308
- }
1309
- models.push(entry);
1310
- }
1311
- return {
1312
- providers: {
1313
- [OPENCODE_PROVIDER_ID]: {
1314
- baseUrl: ctx.baseUrl,
1315
- api: PI_API_DIALECT,
1316
- apiKey: LOOPBACK_API_KEY_PLACEHOLDER,
1317
- models,
1318
- },
1319
- },
1320
- };
1321
- }
1322
-
1323
- /** Extra headers a non-loopback bind needs, or nothing on loopback. */
1324
- function proxyAdmissionHeaders(config: OcxConfig | undefined, envRef: string): Record<string, string> | undefined {
1325
- return shouldInjectApiAuthHeader(config) ? { "x-opencodex-api-key": envRef } : undefined;
1326
- }
1327
-
1328
849
  function buildHermesClientConfig(ctx: ExportContext): HermesGeneratedConfig {
1329
850
  const models: Record<string, HermesModelEntry> = {};
1330
851
  for (const model of normalizeExportModels(ctx.models)) {
@@ -1437,174 +958,6 @@ function buildGajaeClientConfig(ctx: ExportContext): GajaeGeneratedConfig {
1437
958
  };
1438
959
  }
1439
960
 
1440
- const DSH_EFFORT_ORDER: readonly DshReasoningEffort[] = ["low", "medium", "high", "xhigh", "max"];
1441
-
1442
- function dshReasoningEfforts(model: ExportModel): DshModelEntry["reasoningEfforts"] {
1443
- const offered = new Set<string>();
1444
- for (const raw of model.reasoningEfforts ?? []) {
1445
- const effort = raw.trim().toLowerCase();
1446
- if (effort === "ultra" || DSH_EFFORT_ORDER.includes(effort as DshReasoningEffort)) offered.add(effort);
1447
- }
1448
- if (offered.size === 0) return undefined;
1449
- const entries: Array<[DshReasoningEffort, DshWireReasoningEffort]> = [];
1450
- for (const effort of DSH_EFFORT_ORDER) {
1451
- if (effort !== "max") {
1452
- if (offered.has(effort)) entries.push([effort, effort]);
1453
- continue;
1454
- }
1455
- // DSH's key is the selectable level; the value is what it sends on the
1456
- // wire. Preserve OpenCodex's `ultra` spelling when that is the only
1457
- // highest effort, exactly like the rc.6 `max: ultra` contract.
1458
- if (offered.has("max")) entries.push(["max", "max"]);
1459
- else if (offered.has("ultra")) entries.push(["max", "ultra"]);
1460
- }
1461
- return Object.fromEntries(entries);
1462
- }
1463
-
1464
- function isKnownSafeDshCombo(model: ExportModel, config: OcxConfig): boolean {
1465
- const combos = (config as { combos?: unknown }).combos;
1466
- if (typeof combos !== "object" || combos === null || Array.isArray(combos)) return false;
1467
- const combo = (combos as Record<string, unknown>)[model.id];
1468
- if (typeof combo !== "object" || combo === null || Array.isArray(combo)) return false;
1469
- const targets = (combo as { targets?: unknown }).targets;
1470
- if (!Array.isArray(targets) || targets.length === 0) return false;
1471
- return targets.every(target => {
1472
- if (typeof target !== "object" || target === null || Array.isArray(target)) return false;
1473
- const provider = (target as { provider?: unknown }).provider;
1474
- const modelId = (target as { model?: unknown }).model;
1475
- return typeof provider === "string"
1476
- && provider.length > 0
1477
- && provider === provider.trim()
1478
- && provider !== "openai"
1479
- && typeof modelId === "string"
1480
- && modelId.length > 0
1481
- && modelId === modelId.trim();
1482
- });
1483
- }
1484
-
1485
- function buildDshClientConfig(ctx: ExportContext): DshGeneratedConfig {
1486
- const direct = providerCodexAccountMode("openai", ctx.config?.providers?.openai) === "direct";
1487
- const models: DshModelEntry[] = [];
1488
- for (const model of normalizeExportModels(ctx.models)) {
1489
- if (direct && (model.native === true || model.provider === "openai")) continue;
1490
- if (direct && model.provider === "combo" && (!ctx.config || !isKnownSafeDshCombo(model, ctx.config))) continue;
1491
- const input = dshInputModalities(model.inputModalities);
1492
- if (input === null) continue;
1493
- const contextWindow = authoritativeContextWindow(model.contextWindow);
1494
- const reasoningEfforts = dshReasoningEfforts(model);
1495
- models.push({
1496
- id: model.namespaced,
1497
- name: exportModelLabel(model),
1498
- input,
1499
- ...(contextWindow !== undefined ? { contextWindow } : {}),
1500
- ...(reasoningEfforts ? { reasoningEfforts } : {}),
1501
- });
1502
- }
1503
- return {
1504
- "llm-pi-ai": {
1505
- providers: {
1506
- [OPENCODE_PROVIDER_ID]: {
1507
- displayName: "OpenCodex",
1508
- api: "openai-responses",
1509
- baseURL: ctx.baseUrl,
1510
- headers: { Authorization: "Bearer ocx_data_dsh" },
1511
- models,
1512
- },
1513
- },
1514
- },
1515
- };
1516
- }
1517
-
1518
- /**
1519
- * MiniMax Code's `provider add` command persists custom providers under
1520
- * `custom_provider.<id>`. Its current model schema reads `limit.context` for
1521
- * context accounting and `thinking.effortOptions` for the `/model` effort
1522
- * control. Do not emit the removed `thinking.effort` / `defaultEffort` fields:
1523
- * MCode 0.1.6 migrates those into options and keeps the selected effort in the
1524
- * session. Do not emit `defaultModel` either: connecting a client must not
1525
- * silently replace the user's current model selection.
1526
- */
1527
- function buildMcodeClientConfig(ctx: ExportContext): McodeGeneratedConfig {
1528
- const models: Record<string, McodeModelEntry> = {};
1529
- for (const model of normalizeExportModels(ctx.models)) {
1530
- const entry: McodeModelEntry = {};
1531
- const context = authoritativeContextWindow(model.contextWindow);
1532
- if (context !== undefined) entry.limit = { context };
1533
- // `none` is an internal Codex catalog sentinel, not an MCode effort. MCode
1534
- // forwards every option as `output_config.effort` while keeping adaptive
1535
- // thinking enabled, and the Anthropic ingress deliberately accepts only
1536
- // minimal..ultra. Advertising `none` would therefore create a selectable
1537
- // value that cannot disable reasoning and is not forwarded as an effort.
1538
- const efforts = sanitizeCodexReasoningEfforts(model.reasoningEfforts)
1539
- ?.filter(effort => effort !== "none");
1540
- if (efforts && efforts.length > 0) entry.thinking = { effortOptions: efforts };
1541
- models[model.namespaced] = entry;
1542
- }
1543
- return {
1544
- custom_provider: {
1545
- [OPENCODE_PROVIDER_ID]: {
1546
- name: "OpenCodex",
1547
- kind: "custom",
1548
- enabled: true,
1549
- api: "anthropic-messages",
1550
- options: {
1551
- apiKey: LOOPBACK_API_KEY_PLACEHOLDER,
1552
- baseURL: ctx.baseUrl.replace(/\/v1\/?$/, ""),
1553
- authMode: "api-key",
1554
- },
1555
- models,
1556
- },
1557
- },
1558
- };
1559
- }
1560
-
1561
- /**
1562
- * ZCode dials the OpenAI Chat Completions surface (`openai-compatible`), which
1563
- * appends `/chat/completions` to `baseURL`. We supply `baseURL` with the `/v1`
1564
- * suffix so requests land on `/v1/chat/completions`. Model ids are the proxy's canonical
1565
- * `provider/id` selectors, which `/v1/chat/completions` resolves directly. Context
1566
- * limits follow the authoritative-window rule: a model without one ships
1567
- * without `limit` rather than guessing. Modalities are ZCode's observed
1568
- * `text`-floor vocabulary; image-capable rows advertise image input.
1569
- */
1570
- function buildZcodeClientConfig(ctx: ExportContext): ZcodeGeneratedConfig {
1571
- const models: Record<string, ZcodeModelEntry> = {};
1572
- for (const model of normalizeExportModels(ctx.models)) {
1573
- const input = inputModalitiesForClient("pi", model.inputModalities);
1574
- if (input === null) continue;
1575
- const entry: ZcodeModelEntry = {
1576
- name: exportModelLabel(model),
1577
- modalities: { input, output: ["text"] },
1578
- };
1579
- // `limit.context` follows the authoritative-window rule. `output` is
1580
- // deliberately absent: ZCode's schema makes it optional and we have no
1581
- // authoritative output budget to assert (reviewer finding: an emitted
1582
- // stand-in would be a guessed capability, exactly what "no metadata is
1583
- // guessed" forbids).
1584
- const context = authoritativeContextWindow(model.contextWindow);
1585
- if (context !== undefined) {
1586
- entry.limit = { context };
1587
- }
1588
- models[model.namespaced] = entry;
1589
- }
1590
- return {
1591
- provider: {
1592
- [OPENCODE_PROVIDER_ID]: {
1593
- name: "OpenCodex",
1594
- kind: "openai-compatible",
1595
- enabled: true,
1596
- source: "custom",
1597
- options: {
1598
- apiKey: LOOPBACK_API_KEY_PLACEHOLDER,
1599
- baseURL: ctx.baseUrl.replace(/\/v1\/?$/, "") + "/v1",
1600
- apiKeyRequired: true,
1601
- },
1602
- models,
1603
- },
1604
- },
1605
- };
1606
- }
1607
-
1608
961
  /**
1609
962
  * Per-client model counts, read back off the SERIALIZED document rather than
1610
963
  * recomputed from the input rows: `modelsWithoutLimits` drives a GUI line about
@@ -1621,11 +974,6 @@ function summarizePi(document: unknown): { modelCount: number; modelsWithoutLimi
1621
974
  return { modelCount: models.length, modelsWithoutLimits: models.filter(model => model.contextWindow === undefined).length };
1622
975
  }
1623
976
 
1624
- function summarizeOmp(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
1625
- const models = (document as OmpGeneratedConfig | undefined)?.providers?.[OPENCODE_PROVIDER_ID]?.models ?? [];
1626
- return { modelCount: models.length, modelsWithoutLimits: models.filter(model => model.contextWindow === undefined).length };
1627
- }
1628
-
1629
977
  function summarizeHermes(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
1630
978
  const models = (document as HermesGeneratedConfig | undefined)?.providers?.[OPENCODE_PROVIDER_ID]?.models ?? {};
1631
979
  // Hermes carries capability metadata but no per-model limit to be missing.
@@ -1649,26 +997,6 @@ function summarizeGajae(document: unknown): { modelCount: number; modelsWithoutL
1649
997
  return { modelCount: models.length, modelsWithoutLimits: models.filter(model => model.contextWindow === undefined).length };
1650
998
  }
1651
999
 
1652
- function summarizeDsh(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
1653
- const models = (document as DshGeneratedConfig | undefined)?.["llm-pi-ai"]?.providers?.[OPENCODE_PROVIDER_ID]?.models ?? [];
1654
- return { modelCount: models.length, modelsWithoutLimits: models.filter(model => model.contextWindow === undefined).length };
1655
- }
1656
-
1657
- function summarizeMcode(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
1658
- const models = Object.values((document as McodeGeneratedConfig | undefined)?.custom_provider?.[OPENCODE_PROVIDER_ID]?.models ?? {});
1659
- return { modelCount: models.length, modelsWithoutLimits: models.filter(model => !model.limit).length };
1660
- }
1661
-
1662
- function summarizeZcode(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
1663
- const models = Object.values((document as ZcodeGeneratedConfig | undefined)?.provider?.[OPENCODE_PROVIDER_ID]?.models ?? {});
1664
- return { modelCount: models.length, modelsWithoutLimits: models.filter(model => !model.limit).length };
1665
- }
1666
-
1667
- /** One fragment at `path`, built from this client's own document. */
1668
- function singleFragment(clientId: ExportClientId, path: readonly string[], value: unknown): ManagedContribution {
1669
- return { clientId, fragments: [{ path, value }] };
1670
- }
1671
-
1672
1000
  function buildOpencodeContribution(ctx: ExportContext): ManagedContribution {
1673
1001
  const doc = buildOpencodeClientConfig(ctx);
1674
1002
  return {
@@ -1688,11 +1016,6 @@ function buildPiContribution(ctx: ExportContext): ManagedContribution {
1688
1016
  return singleFragment("pi", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]);
1689
1017
  }
1690
1018
 
1691
- function buildOmpContribution(ctx: ExportContext): ManagedContribution {
1692
- const doc = buildOmpClientConfig(ctx);
1693
- return singleFragment("omp", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]);
1694
- }
1695
-
1696
1019
  function buildHermesContribution(ctx: ExportContext): ManagedContribution {
1697
1020
  const doc = buildHermesClientConfig(ctx);
1698
1021
  return singleFragment("hermes", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]);
@@ -1724,21 +1047,6 @@ function buildGajaeContribution(ctx: ExportContext): ManagedContribution {
1724
1047
  return singleFragment("gajae", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]);
1725
1048
  }
1726
1049
 
1727
- function buildDshContribution(ctx: ExportContext): ManagedContribution {
1728
- const doc = buildDshClientConfig(ctx);
1729
- return singleFragment("dsh", ["llm-pi-ai", "providers", OPENCODE_PROVIDER_ID], doc["llm-pi-ai"].providers[OPENCODE_PROVIDER_ID]);
1730
- }
1731
-
1732
- function buildMcodeContribution(ctx: ExportContext): ManagedContribution {
1733
- const doc = buildMcodeClientConfig(ctx);
1734
- return singleFragment("mcode", ["custom_provider", OPENCODE_PROVIDER_ID], doc.custom_provider[OPENCODE_PROVIDER_ID]);
1735
- }
1736
-
1737
- function buildZcodeContribution(ctx: ExportContext): ManagedContribution {
1738
- const doc = buildZcodeClientConfig(ctx);
1739
- return singleFragment("zcode", ["provider", OPENCODE_PROVIDER_ID], doc.provider[OPENCODE_PROVIDER_ID]);
1740
- }
1741
-
1742
1050
  /**
1743
1051
  * Prime Agent (PrimeIntellect) is the pi coding agent shipped under a different
1744
1052
  * brand rather than a lookalike: its package declares a `piConfig` block, and