@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.
- package/AGENTS_INSTALL.md +2 -2
- package/README.md +31 -0
- package/bin/ocx.mjs +10 -1
- package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
- package/gui/dist/assets/index-Djowl68T.js +112 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-codec.ts +304 -0
- package/src/adapters/anthropic-image-normalize.ts +8 -298
- package/src/adapters/anthropic.ts +30 -7
- package/src/adapters/command-code.ts +7 -5
- package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
- package/src/adapters/cursor/images.ts +36 -6
- package/src/adapters/cursor/live-transport.ts +7 -2
- package/src/adapters/cursor/native-exec-desktop.ts +2 -15
- package/src/adapters/cursor/protobuf-request.ts +13 -3
- package/src/adapters/cursor/tool-definitions.ts +5 -670
- package/src/adapters/cursor/tool-guidance.ts +236 -0
- package/src/adapters/cursor/tool-naming.ts +252 -0
- package/src/adapters/cursor/tool-schemas.ts +195 -0
- package/src/adapters/cursor/types.ts +6 -3
- package/src/adapters/exec-tool-result-normalize.ts +1 -1
- package/src/adapters/google-errors.ts +9 -1
- package/src/adapters/google.ts +1 -0
- package/src/adapters/kiro-calibration.ts +181 -0
- package/src/adapters/kiro.ts +135 -3
- package/src/adapters/openai-responses.ts +216 -26
- package/src/adapters/responses-code-mode.ts +59 -0
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-schema-analysis.ts +86 -0
- package/src/adapters/xai-tool-schema.ts +2 -87
- package/src/adapters/xai-web-search.ts +1 -1
- package/src/bridge.ts +22 -10
- package/src/chat/inbound.ts +11 -3
- package/src/claude/inbound-content-options.ts +60 -0
- package/src/claude/inbound-model-options.ts +142 -0
- package/src/claude/inbound-records.ts +7 -0
- package/src/claude/inbound.ts +10 -202
- package/src/claude/model-info.ts +45 -0
- package/src/cli/account-auth.ts +21 -6
- package/src/cli/capabilities.ts +13 -2
- package/src/cli/claude.ts +232 -39
- package/src/cli/config-command.ts +9 -1
- package/src/cli/dispatch.ts +5 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/effort.ts +372 -0
- package/src/cli/export-command.ts +3 -9
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/init.ts +4 -0
- package/src/cli/model-selection-guidance.ts +30 -0
- package/src/cli/models-runtime.ts +3 -2
- package/src/cli/models.ts +8 -3
- package/src/cli/opencode.ts +4 -1
- package/src/cli/provider-runtime.ts +65 -0
- package/src/cli/provider.ts +8 -0
- package/src/cli/registry.ts +16 -2
- package/src/cli/runtime-api.ts +3 -1
- package/src/cli/star-prompt.ts +22 -6
- package/src/cli/status-probes.ts +168 -0
- package/src/cli/status.ts +5 -168
- package/src/clients/config-export/constants.ts +69 -0
- package/src/clients/config-export/contracts.ts +154 -0
- package/src/clients/config-export/dsh.ts +132 -0
- package/src/clients/config-export/fast-models.ts +29 -0
- package/src/clients/config-export/mcode.ts +83 -0
- package/src/clients/config-export/model-metadata.ts +108 -0
- package/src/clients/config-export/omp.ts +104 -0
- package/src/clients/config-export/zcode.ts +92 -0
- package/src/clients/config-export.ts +18 -710
- package/src/codex/account-lifecycle.ts +20 -3
- package/src/codex/account-usability.ts +2 -0
- package/src/codex/auth-api.ts +170 -23
- package/src/codex/auth-context.ts +328 -24
- package/src/codex/catalog/effort.ts +30 -4
- package/src/codex/catalog/metadata.ts +39 -9
- package/src/codex/catalog/native-models.ts +53 -12
- package/src/codex/catalog/parsing.ts +119 -5
- package/src/codex/catalog/provider-fetch.ts +150 -20
- package/src/codex/catalog/reserve.ts +52 -0
- package/src/codex/catalog/sync.ts +89 -16
- package/src/codex/convergence-types.ts +1 -0
- package/src/codex/convergence.ts +2 -0
- package/src/codex/data/upstream-models.json +1 -1
- package/src/codex/forward-transport-headers.ts +25 -0
- package/src/codex/inject.ts +3 -28
- package/src/codex/legacy-config-keys.ts +68 -0
- package/src/codex/log-guard/inspect-schema.ts +137 -0
- package/src/codex/log-guard/inspect.ts +2 -134
- package/src/codex/loopback-target.ts +54 -0
- package/src/codex/main-account-cache.ts +63 -1
- package/src/codex/main-account-hard-lock.ts +52 -0
- package/src/codex/main-account.ts +3 -1
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/model-entitlements.ts +54 -4
- package/src/codex/project-config-warnings.ts +92 -2
- package/src/codex/prompt-layers/encoding.ts +80 -0
- package/src/codex/prompt-layers/paths.ts +54 -0
- package/src/codex/prompt-layers/revision.ts +55 -0
- package/src/codex/prompt-layers/toml-edit.ts +163 -0
- package/src/codex/prompt-layers/toml-read.ts +181 -0
- package/src/codex/prompt-layers.ts +14 -520
- package/src/codex/quota-auto-refresh-state.ts +16 -0
- package/src/codex/quota-auto-refresh.ts +219 -0
- package/src/codex/quota-types.ts +51 -0
- package/src/codex/quota.ts +252 -93
- package/src/codex/reserve-availability.ts +177 -0
- package/src/codex/routing.ts +28 -9
- package/src/codex/shim.ts +53 -11
- package/src/codex/subagent-model-fallback.ts +23 -3
- package/src/combos/failover.ts +125 -7
- package/src/combos/identifiers.ts +89 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/resolve.ts +80 -9
- package/src/combos/types.ts +20 -93
- package/src/config/subagent-models.ts +24 -0
- package/src/config.ts +156 -13
- package/src/generated/compatibility-version.json +454 -166
- package/src/generated/model-metadata.ts +1 -1
- package/src/integrations/journal.ts +65 -4
- package/src/integrations/store.ts +5 -0
- package/src/lab/events/limits.ts +4 -0
- package/src/lib/destination-policy.ts +31 -2
- package/src/lib/errors.ts +39 -0
- package/src/lib/provider-outbound.ts +69 -3
- package/src/lib/proxy-env.ts +22 -0
- package/src/lib/redact-folding.ts +176 -0
- package/src/lib/redact.ts +2 -175
- package/src/lib/state-store-sweeper.ts +20 -6
- package/src/lib/token-estimate.ts +94 -27
- package/src/lib/windows-user-principal.ts +53 -5
- package/src/oauth/anthropic-routing.ts +99 -3
- package/src/oauth/generic-account-failover.ts +36 -13
- package/src/oauth/index.ts +127 -26
- package/src/oauth/login-cli.ts +5 -0
- package/src/oauth/meta-muse.ts +117 -15
- package/src/oauth/pool-settings-capability.ts +15 -4
- package/src/providers/api-keys.ts +8 -10
- package/src/providers/default-aliases.ts +39 -0
- package/src/providers/derive.ts +10 -2
- package/src/providers/fastwire.ts +36 -7
- package/src/providers/initial-model-selection-runtime.ts +90 -0
- package/src/providers/initial-model-selection.ts +120 -0
- package/src/providers/key-failover.ts +134 -54
- package/src/providers/key-store.ts +11 -1
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery.ts +76 -0
- package/src/providers/model-rename-startup.ts +72 -8
- package/src/providers/openai-sidecar.ts +17 -5
- package/src/providers/openai-tiers-destination.ts +102 -0
- package/src/providers/openai-tiers.ts +2 -99
- package/src/providers/opencode-go-transport.ts +41 -0
- package/src/providers/quota-key-accounts.ts +141 -0
- package/src/providers/quota-types.ts +9 -0
- package/src/providers/quota.ts +535 -104
- package/src/providers/registry.ts +60 -17
- package/src/providers/xai-responses-opt-in.ts +31 -5
- package/src/quota/reset-activation.ts +81 -0
- package/src/quota/reset-detector.ts +305 -0
- package/src/quota/reset-notify-config.ts +162 -0
- package/src/quota/reset-observer.ts +125 -0
- package/src/quota/reset-poller.ts +160 -0
- package/src/quota/reset-seen-store.ts +385 -0
- package/src/quota/reset-sinks.ts +199 -0
- package/src/quota/window-mapping.ts +106 -0
- package/src/responses/apply-patch-envelope.ts +46 -0
- package/src/responses/code-mode-helper-compat.ts +39 -1
- package/src/responses/custom-tool-compat.ts +10 -4
- package/src/responses/hosted-tool-policy.ts +12 -4
- package/src/responses/parser-content.ts +133 -0
- package/src/responses/parser-text-format.ts +24 -0
- package/src/responses/parser-tools.ts +188 -0
- package/src/responses/parser.ts +3 -326
- package/src/responses/state.ts +124 -28
- package/src/router.ts +48 -13
- package/src/routing/analytics.ts +1 -0
- package/src/routing/capability.ts +17 -4
- package/src/server/auth-cors.ts +7 -1
- package/src/server/background-lifecycle.ts +23 -1
- package/src/server/chat-completions.ts +25 -3
- package/src/server/claude-messages.ts +62 -5
- package/src/server/effort-row.ts +1 -1
- package/src/server/fast-row.ts +295 -0
- package/src/server/gui-static.ts +30 -4
- package/src/server/index.ts +107 -21
- package/src/server/management/agent-settings-routes.ts +2 -2
- package/src/server/management/combo-routes.ts +37 -9
- package/src/server/management/config-routes.ts +93 -2
- package/src/server/management/integration-routes.ts +108 -0
- package/src/server/management/model-routes.ts +13 -3
- package/src/server/management/model-rows.ts +20 -1
- package/src/server/management/native-integration-routes.ts +4 -1
- package/src/server/management/oauth-account-routes.ts +38 -10
- package/src/server/management/provider-routes.ts +34 -3
- package/src/server/management/quota-reset-routes.ts +57 -0
- package/src/server/management/route-registry.ts +7 -4
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/system-routes.ts +3 -2
- package/src/server/management-api.ts +14 -2
- package/src/server/ports.ts +12 -2
- package/src/server/relay-eager.ts +38 -23
- package/src/server/relay.ts +4 -0
- package/src/server/request-log.ts +6 -0
- package/src/server/responses/codex-ws-correlation.ts +65 -0
- package/src/server/responses/codex-ws-exchange.ts +261 -0
- package/src/server/responses/codex-ws-metadata.ts +134 -0
- package/src/server/responses/codex-ws-pool.ts +162 -0
- package/src/server/responses/codex-ws-request.ts +87 -0
- package/src/server/responses/codex-ws-session.ts +93 -0
- package/src/server/responses/codex-ws-wire.ts +144 -0
- package/src/server/responses/collaboration.ts +41 -1
- package/src/server/responses/compact.ts +105 -12
- package/src/server/responses/core.ts +476 -57
- package/src/server/responses/fetch-helpers.ts +10 -3
- package/src/server/responses/input-admission.ts +16 -9
- package/src/server/responses/responses-field-backfill.ts +1 -1
- package/src/server/responses/ws-upstream.ts +34 -318
- package/src/server/responses-custom-tool-repair.ts +20 -4
- package/src/server/responses-undeclared-tool-guard.ts +100 -8
- package/src/server/safe-response-headers.ts +23 -0
- package/src/server/search.ts +9 -0
- package/src/server/subagent-models-startup.ts +27 -0
- package/src/server/system-env-shell.ts +238 -0
- package/src/server/system-env.ts +7 -234
- package/src/server/ws-bridge.ts +3 -25
- package/src/server/xai-responses-startup.ts +21 -0
- package/src/service-manager-probe.ts +1 -1
- package/src/service.ts +55 -16
- package/src/types/config.ts +108 -12
- package/src/types/provider.ts +36 -7
- package/src/types/tools.ts +26 -1
- package/src/types.ts +2 -0
- package/src/update/notify.ts +8 -2
- package/src/usage/cost.ts +38 -28
- package/src/usage/expected-prices.ts +34 -15
- package/src/usage/log.ts +2 -0
- package/src/usage/model-identity.ts +26 -0
- package/src/usage/summary.ts +15 -1
- package/src/vision/describe.ts +6 -0
- package/src/vision/image-rewrite.ts +108 -0
- package/src/vision/index.ts +19 -306
- package/src/vision/plan.ts +205 -0
- package/src/web-search/executor.ts +6 -0
- package/src/web-search/index.ts +8 -1
- package/gui/dist/assets/index-BU1tE0sr.js +0 -112
- 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 {
|
|
28
|
-
import {
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|