@bitkyc08/opencodex 2.10.0 → 2.10.1
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 +77 -0
- package/README.md +4 -10
- package/bin/ocx.mjs +71 -18
- package/gui/dist/assets/index-Cd6_PBKn.css +1 -0
- package/gui/dist/assets/index-ChZQsmBY.js +70 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +8 -4
- package/src/adapters/anthropic.ts +208 -14
- package/src/adapters/base.ts +16 -5
- package/src/adapters/cursor/effort-map.ts +3 -2
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/live-transport.ts +105 -95
- package/src/adapters/cursor/native-exec.ts +32 -6
- package/src/adapters/cursor/protobuf-request.ts +20 -15
- package/src/adapters/cursor/request-builder.ts +21 -7
- package/src/adapters/cursor/types.ts +7 -0
- package/src/adapters/google-antigravity-replay.ts +237 -21
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +50 -9
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +10 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +37 -11
- package/src/adapters/openai-chat.ts +284 -83
- package/src/adapters/openai-responses.ts +182 -24
- package/src/bridge.ts +177 -7
- package/src/chat/outbound.ts +78 -23
- package/src/claude/agents-inject.ts +27 -5
- package/src/claude/inbound.ts +11 -1
- package/src/claude/model-info.ts +13 -10
- package/src/claude/outbound.ts +17 -0
- package/src/cli/account-api.ts +24 -0
- package/src/cli/account-auth.ts +31 -6
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +5 -0
- package/src/cli/claude.ts +2 -1
- package/src/cli/doctor.ts +93 -22
- package/src/cli/export-command.ts +26 -12
- package/src/cli/help.ts +8 -6
- package/src/cli/index.ts +56 -22
- package/src/cli/integrations.ts +84 -1
- package/src/cli/observe.ts +54 -1
- package/src/cli/opencode.ts +2 -1
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +1 -1
- package/src/cli/v2.ts +36 -9
- package/src/clients/config-export.ts +687 -10
- package/src/codex/account-lifecycle.ts +30 -5
- package/src/codex/account-usability.ts +22 -2
- package/src/codex/admission.ts +255 -0
- package/src/codex/auth-api.ts +427 -140
- package/src/codex/auth-context.ts +155 -30
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +62 -0
- package/src/codex/catalog/aggregation.ts +14 -1
- package/src/codex/catalog/bundled.ts +282 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/metadata.ts +51 -6
- package/src/codex/catalog/parsing.ts +6 -3
- package/src/codex/catalog/provider-fetch.ts +576 -41
- package/src/codex/catalog/sync.ts +505 -66
- package/src/codex/catalog-admission.ts +197 -0
- package/src/codex/catalog-write-serialization.ts +241 -0
- package/src/codex/catalog.ts +2 -1
- package/src/codex/codex-write-lock.ts +372 -0
- package/src/codex/convergence-types.ts +593 -0
- package/src/codex/convergence.ts +441 -0
- package/src/codex/desired-state.ts +177 -0
- package/src/codex/features.ts +52 -8
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +257 -0
- package/src/codex/history-lock.ts +241 -0
- package/src/codex/history-migration-guardian.ts +18 -5
- package/src/codex/history-provider.ts +9 -2
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +176 -0
- package/src/codex/inject-coordination.ts +245 -0
- package/src/codex/inject.ts +605 -124
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +10 -1
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/management-convergence.ts +114 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +167 -0
- package/src/codex/native-main-lock-file.ts +158 -0
- package/src/codex/native-main-owner.ts +315 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1512 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +340 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +557 -0
- package/src/codex/project-config-warnings.ts +18 -4
- package/src/codex/prompt-journal.ts +311 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +224 -0
- package/src/codex/quota.ts +86 -3
- package/src/codex/routing.ts +299 -62
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +39 -13
- package/src/codex/subagent-model-fallback.ts +73 -12
- package/src/codex/transition-state.ts +604 -0
- package/src/codex/upstream-host-health.ts +70 -0
- package/src/codex/user-identity.ts +266 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/config.ts +562 -26
- package/src/generated/jawcode-model-metadata.ts +2 -2
- package/src/grok/inject.ts +15 -4
- package/src/grok/inspect.ts +45 -0
- package/src/images/loop.ts +113 -20
- package/src/integrations/config-io.ts +151 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/native/ownership-preflight.ts +165 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +101 -0
- package/src/integrations/serialize.ts +235 -0
- package/src/integrations/state.ts +290 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer.ts +492 -0
- package/src/lib/bounded-body.ts +46 -8
- package/src/lib/bun-runtime.ts +110 -1
- package/src/lib/bun-stream-caps.ts +2 -1
- package/src/lib/redact.ts +407 -2
- package/src/lib/shadow-call.ts +24 -0
- package/src/lib/translator-budget.ts +10 -0
- package/src/lib/upstream-reachability.ts +91 -0
- package/src/lib/upstream-retry.ts +154 -2
- package/src/lib/windows-secret-acl.ts +212 -11
- package/src/lib/winsw.ts +9 -3
- package/src/oauth/index.ts +61 -3
- package/src/oauth/key-providers.ts +4 -0
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/store.ts +31 -6
- package/src/oauth/token-guardian.ts +9 -3
- package/src/providers/codex-capacity.ts +288 -0
- package/src/providers/derive.ts +33 -1
- package/src/providers/free-directory.ts +3 -1
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/openai-sidecar.ts +64 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/quota.ts +334 -26
- package/src/providers/registry.ts +284 -16
- package/src/providers/xai-transport.ts +11 -4
- package/src/responses/compaction.ts +8 -1
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/parser.ts +44 -2
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +105 -0
- package/src/responses/spill-store.ts +45 -8
- package/src/responses/state.ts +161 -17
- package/src/router.ts +302 -16
- package/src/routing/analytics.ts +377 -0
- package/src/routing/capability.ts +204 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +444 -0
- package/src/routing/health.ts +401 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +590 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile.ts +423 -0
- package/src/routing/quota.ts +124 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +686 -0
- package/src/server/auth-cors.ts +46 -6
- package/src/server/chat-completions.ts +28 -13
- package/src/server/claude-messages.ts +23 -15
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +10 -1
- package/src/server/index.ts +238 -52
- package/src/server/lifecycle.ts +228 -9
- package/src/server/live.ts +14 -3
- package/src/server/management/agent-settings-routes.ts +64 -14
- package/src/server/management/combo-routes.ts +5 -5
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/context.ts +42 -2
- package/src/server/management/integration-routes.ts +538 -0
- package/src/server/management/logs-usage-routes.ts +1 -1
- package/src/server/management/model-routes.ts +32 -113
- package/src/server/management/model-rows.ts +117 -0
- package/src/server/management/native-integration-routes.ts +587 -0
- package/src/server/management/oauth-account-routes.ts +1 -1
- package/src/server/management/provider-routes.ts +218 -117
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +177 -0
- package/src/server/management/shared.ts +2 -2
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +276 -24
- package/src/server/management/system-routes.ts +4 -0
- package/src/server/management-api.ts +84 -9
- package/src/server/management-auth.ts +43 -5
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +120 -6
- package/src/server/request-log.ts +26 -6
- package/src/server/responses/collaboration.ts +63 -8
- package/src/server/responses/compact.ts +272 -41
- package/src/server/responses/core.ts +730 -132
- package/src/server/responses/fetch-helpers.ts +15 -1
- package/src/server/responses-item-id-repair.ts +32 -3
- package/src/server/responses-json-events.ts +52 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/search.ts +51 -6
- package/src/server/sse-payload-rewrite.ts +89 -12
- package/src/server/startup-health-cache.ts +7 -1
- package/src/server/ws-bridge.ts +11 -17
- package/src/service-manager-probe.ts +297 -0
- package/src/service.ts +222 -32
- package/src/tray/windows-tray.ps1 +9 -0
- package/src/tray/windows.ts +15 -7
- package/src/types.ts +194 -14
- package/src/update/index.ts +13 -13
- package/src/update/job.ts +24 -21
- package/src/update/notify.ts +7 -3
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +129 -10
- package/src/usage/log.ts +50 -15
- package/src/usage/summary.ts +4 -4
- package/src/vision/index.ts +6 -1
- package/src/web-search/loop.ts +161 -34
- package/gui/dist/assets/index-OY43ubAq.css +0 -1
- package/gui/dist/assets/index-YwNnKZcL.js +0 -67
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
|
@@ -90,7 +90,7 @@ import {
|
|
|
90
90
|
EXPORT_CLIENTS,
|
|
91
91
|
EXPORT_CLIENT_IDS,
|
|
92
92
|
OPENCODE_PROVIDER_ID,
|
|
93
|
-
|
|
93
|
+
buildClientConfigText,
|
|
94
94
|
isExportClientId,
|
|
95
95
|
opencodeProxyBaseUrl,
|
|
96
96
|
} from "../../clients/config-export";
|
|
@@ -104,93 +104,9 @@ import type {
|
|
|
104
104
|
import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
|
|
105
105
|
import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
|
|
106
106
|
import type { ManagementContext } from "./context";
|
|
107
|
+
import { listManagementModelRows, loadExportModels } from "./model-rows";
|
|
107
108
|
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
108
109
|
|
|
109
|
-
/**
|
|
110
|
-
* One row of the `/api/models` list. Routed rows spread a `CatalogModel`, so the shape is
|
|
111
|
-
* that model plus the identity/visibility fields this boundary computes for every row
|
|
112
|
-
* regardless of source. `disabled` is always present; the rest vary by row origin.
|
|
113
|
-
*/
|
|
114
|
-
type ManagementModelRow = Partial<CatalogModel> & {
|
|
115
|
-
provider: string;
|
|
116
|
-
id: string;
|
|
117
|
-
namespaced: string;
|
|
118
|
-
disabled: boolean;
|
|
119
|
-
native?: boolean;
|
|
120
|
-
custom?: boolean;
|
|
121
|
-
customId?: string;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* The exact row list `/api/models` returns. Extracted so `/api/client-config` exports the
|
|
126
|
-
* models the GUI's Models tab shows — including this function's `disabled` computation,
|
|
127
|
-
* which the export core (src/clients/config-export.ts) deliberately does not perform.
|
|
128
|
-
*/
|
|
129
|
-
async function listManagementModelRows(config: OcxConfig): Promise<ManagementModelRow[]> {
|
|
130
|
-
const models = await fetchAllModels(config);
|
|
131
|
-
const disabled = new Set(config.disabledModels ?? []);
|
|
132
|
-
// Native GPT passthrough rows lead (provider "openai", bare-slug namespaced ids): sourced
|
|
133
|
-
// from the static supported set so a disabled model stays listed and re-enableable.
|
|
134
|
-
const native: ManagementModelRow[] = nativeModelRows(config).map(row => ({
|
|
135
|
-
provider: "openai",
|
|
136
|
-
id: row.slug,
|
|
137
|
-
namespaced: row.slug,
|
|
138
|
-
disabled: row.disabled,
|
|
139
|
-
native: true,
|
|
140
|
-
...(row.contextWindow !== undefined ? { contextWindow: row.contextWindow } : {}),
|
|
141
|
-
}));
|
|
142
|
-
const customModels: ManagementModelRow[] = (config.customModels ?? []).map(cm => {
|
|
143
|
-
const namespaced = routedSlug(cm.provider, cm.modelId);
|
|
144
|
-
return {
|
|
145
|
-
provider: cm.provider,
|
|
146
|
-
id: cm.modelId,
|
|
147
|
-
namespaced,
|
|
148
|
-
disabled: [...disabled].some(stored => slugEquals(stored, cm.provider, cm.modelId)),
|
|
149
|
-
custom: true,
|
|
150
|
-
customId: cm.id,
|
|
151
|
-
displayName: cm.displayName,
|
|
152
|
-
...(cm.contextWindow ? { contextWindow: cm.contextWindow } : {}),
|
|
153
|
-
...(cm.inputModalities ? { inputModalities: cm.inputModalities } : {}),
|
|
154
|
-
};
|
|
155
|
-
});
|
|
156
|
-
const publicModels = uniqueCatalogModelsForPublicList(models);
|
|
157
|
-
const comboNamespaced = new Set(
|
|
158
|
-
publicModels.filter(model => model.provider === "combo").map(catalogModelSlug),
|
|
159
|
-
);
|
|
160
|
-
const visibleCustomModels = customModels.filter(model => !comboNamespaced.has(model.namespaced));
|
|
161
|
-
// Custom metadata wins when a physical live/static row resolves to the same Codex-facing
|
|
162
|
-
// slug, while a combo keeps the same precedence it has in routing and /v1/models.
|
|
163
|
-
const customNamespaced = new Set(visibleCustomModels.map(c => c.namespaced));
|
|
164
|
-
const dedupedRouted = publicModels.map((m): ManagementModelRow | null => {
|
|
165
|
-
// Codex-facing slug (one "/", slug-codec); disabledModels compares tolerate both forms.
|
|
166
|
-
const namespaced = catalogModelSlug(m);
|
|
167
|
-
if (m.provider !== "combo" && customNamespaced.has(namespaced)) return null;
|
|
168
|
-
const contextCap = providerContextCap(config, m.provider);
|
|
169
|
-
return {
|
|
170
|
-
...m,
|
|
171
|
-
namespaced,
|
|
172
|
-
disabled: [...disabled].some(stored => (
|
|
173
|
-
stored === namespaced || slugEquals(stored, m.provider, m.id)
|
|
174
|
-
)),
|
|
175
|
-
...(contextCap !== undefined ? { contextCap, contextCapped: m.contextCapped === true } : {}),
|
|
176
|
-
};
|
|
177
|
-
}).filter((row): row is ManagementModelRow => row !== null);
|
|
178
|
-
return [...native, ...dedupedRouted, ...visibleCustomModels];
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** `/api/models` row → the narrower input the client-config serializers accept. */
|
|
182
|
-
function toExportModel(row: ManagementModelRow): ExportModel {
|
|
183
|
-
return {
|
|
184
|
-
namespaced: row.namespaced,
|
|
185
|
-
provider: row.provider,
|
|
186
|
-
id: row.id,
|
|
187
|
-
...(row.native ? { native: true } : {}),
|
|
188
|
-
...(row.displayName ? { displayName: row.displayName } : {}),
|
|
189
|
-
...(row.contextWindow !== undefined ? { contextWindow: row.contextWindow } : {}),
|
|
190
|
-
...(row.inputModalities ? { inputModalities: row.inputModalities } : {}),
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
110
|
/**
|
|
195
111
|
* Counts read back off the SERIALIZED document rather than recomputed from the input rows.
|
|
196
112
|
* `modelsWithoutLimits` drives a GUI line claiming "these models ship without limits", so it
|
|
@@ -198,17 +114,14 @@ function toExportModel(row: ManagementModelRow): ExportModel {
|
|
|
198
114
|
* core's "authoritative context window" rule would be free to drift from it silently.
|
|
199
115
|
*/
|
|
200
116
|
function summarizeExportedModels(client: ExportClientId, document: unknown): { modelCount: number; modelsWithoutLimits: number } {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
const models = (document as PiGeneratedConfig).providers[OPENCODE_PROVIDER_ID].models;
|
|
207
|
-
return { modelCount: models.length, modelsWithoutLimits: models.filter(entry => entry.contextWindow === undefined).length };
|
|
117
|
+
// Each client counts its own document shape. The previous branch assumed
|
|
118
|
+
// "anything that is not OpenCode must be Pi", which silently misread the
|
|
119
|
+
// moment a third client existed.
|
|
120
|
+
return EXPORT_CLIENTS[client].summarize(document);
|
|
208
121
|
}
|
|
209
122
|
|
|
210
123
|
export async function handleModelRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
211
|
-
const { req, url, config, deps,
|
|
124
|
+
const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
|
|
212
125
|
// A handler persists the exact config object passed in. Production defaults to
|
|
213
126
|
// the real store; tests that pass an in-memory fixture inject a no-op/spy. Do not
|
|
214
127
|
// bypass this seam with a dynamic config import — doing so replaced a user's
|
|
@@ -249,9 +162,12 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
249
162
|
);
|
|
250
163
|
}
|
|
251
164
|
const spec = EXPORT_CLIENTS[requested];
|
|
252
|
-
let
|
|
165
|
+
let models: ExportModel[];
|
|
253
166
|
try {
|
|
254
|
-
|
|
167
|
+
// The ONE loader every export surface uses. It carries the visibility
|
|
168
|
+
// filter with it, so this route and the integration routes cannot end up
|
|
169
|
+
// telling a client about different models.
|
|
170
|
+
models = await loadExportModels(config);
|
|
255
171
|
} catch (error) {
|
|
256
172
|
// A partial or empty `models` block reads as a valid config while offering nothing,
|
|
257
173
|
// so a catalog failure is surfaced as unavailable rather than serialized. The
|
|
@@ -264,21 +180,24 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
264
180
|
config,
|
|
265
181
|
);
|
|
266
182
|
}
|
|
267
|
-
|
|
268
|
-
// user disabled in the Models tab is absent from /v1/models, so exporting it would hand the
|
|
269
|
-
// client a selector the proxy refuses to route.
|
|
270
|
-
const models = rows.filter(row => !row.disabled).map(toExportModel);
|
|
271
|
-
const document = buildClientConfig(requested, {
|
|
183
|
+
const built = buildClientConfigText(requested, {
|
|
272
184
|
baseUrl: opencodeProxyBaseUrl(Number(url.port) || config.port, config.hostname),
|
|
273
185
|
models,
|
|
274
186
|
config,
|
|
275
187
|
});
|
|
188
|
+
const document = built.document;
|
|
276
189
|
return jsonResponse({
|
|
277
190
|
client: spec.id,
|
|
278
191
|
filename: spec.filename,
|
|
279
192
|
destination: spec.destination(process.env),
|
|
280
193
|
apiKeyEnv: spec.apiKeyEnv,
|
|
281
194
|
exportHint: spec.exportHint,
|
|
195
|
+
// The client's own format and the exact bytes for it. The GUI previously
|
|
196
|
+
// re-serialized `config` as JSON, which is wrong for four of the six
|
|
197
|
+
// clients; `mediaType` also drives the download blob.
|
|
198
|
+
format: built.format,
|
|
199
|
+
mediaType: built.mediaType,
|
|
200
|
+
text: built.text,
|
|
282
201
|
...summarizeExportedModels(requested, document),
|
|
283
202
|
config: document,
|
|
284
203
|
}, 200, req, config);
|
|
@@ -292,8 +211,8 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
292
211
|
const disabled = Array.isArray(body.models) ? body.models.filter((m): m is string => typeof m === "string") : [];
|
|
293
212
|
config.disabledModels = disabled;
|
|
294
213
|
persistConfig(config);
|
|
295
|
-
await
|
|
296
|
-
return jsonResponse({ ok: true, disabled });
|
|
214
|
+
const catalogRefresh = await convergeCodexCatalog();
|
|
215
|
+
return jsonResponse({ ok: true, disabled, catalogRefresh });
|
|
297
216
|
}
|
|
298
217
|
|
|
299
218
|
// One user-facing visibility switch spans two persisted filters: a provider allowlist and the
|
|
@@ -391,8 +310,8 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
391
310
|
|
|
392
311
|
config.disabledModels = disabled;
|
|
393
312
|
persistConfig(config);
|
|
394
|
-
await
|
|
395
|
-
return jsonResponse({ ok: true, scope, provider, enabled: body.enabled, disabled });
|
|
313
|
+
const catalogRefresh = await convergeCodexCatalog();
|
|
314
|
+
return jsonResponse({ ok: true, scope, provider, enabled: body.enabled, disabled, catalogRefresh });
|
|
396
315
|
}
|
|
397
316
|
|
|
398
317
|
if (url.pathname === "/api/custom-models" && req.method === "GET") {
|
|
@@ -430,8 +349,8 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
430
349
|
};
|
|
431
350
|
config.customModels = [...existing, entry];
|
|
432
351
|
persistConfig(config);
|
|
433
|
-
await
|
|
434
|
-
return jsonResponse(entry, 201);
|
|
352
|
+
const catalogRefresh = await convergeCodexCatalog();
|
|
353
|
+
return jsonResponse({ ...entry, catalogRefresh }, 201);
|
|
435
354
|
}
|
|
436
355
|
|
|
437
356
|
const customPutMatch = url.pathname.match(/^\/api\/custom-models\/([^/]+)$/);
|
|
@@ -468,8 +387,8 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
468
387
|
list[idx] = cm;
|
|
469
388
|
config.customModels = list;
|
|
470
389
|
persistConfig(config);
|
|
471
|
-
await
|
|
472
|
-
return jsonResponse(cm);
|
|
390
|
+
const catalogRefresh = await convergeCodexCatalog();
|
|
391
|
+
return jsonResponse({ ...cm, catalogRefresh });
|
|
473
392
|
}
|
|
474
393
|
|
|
475
394
|
const customDelMatch = url.pathname.match(/^\/api\/custom-models\/([^/]+)$/);
|
|
@@ -482,8 +401,8 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
482
401
|
list.splice(idx, 1);
|
|
483
402
|
config.customModels = list.length > 0 ? list : undefined;
|
|
484
403
|
persistConfig(config);
|
|
485
|
-
await
|
|
486
|
-
return jsonResponse({ ok: true });
|
|
404
|
+
const catalogRefresh = await convergeCodexCatalog();
|
|
405
|
+
return jsonResponse({ ok: true, catalogRefresh });
|
|
487
406
|
}
|
|
488
407
|
|
|
489
408
|
// Per-provider catalog allowlist (issue #52): when a provider has a non-empty selectedModels list,
|
|
@@ -518,8 +437,8 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
518
437
|
if (models.length > 0) config.providers[provider].selectedModels = models;
|
|
519
438
|
else delete config.providers[provider].selectedModels;
|
|
520
439
|
persistConfig(config);
|
|
521
|
-
await
|
|
522
|
-
return jsonResponse({ ok: true, provider, selected: models });
|
|
440
|
+
const catalogRefresh = await convergeCodexCatalog();
|
|
441
|
+
return jsonResponse({ ok: true, provider, selected: models, catalogRefresh });
|
|
523
442
|
}
|
|
524
443
|
return null;
|
|
525
444
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/api/models` row list and its projection into export models.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from model-routes.ts so `/api/client-config` and the integration
|
|
5
|
+
* routes read the SAME visible-model list. Two callers computing "which models
|
|
6
|
+
* does this user actually have" independently is how the export and the toggle
|
|
7
|
+
* would quietly disagree about what a client was told.
|
|
8
|
+
*
|
|
9
|
+
* Bodies are unchanged from their previous home; only `export` was added.
|
|
10
|
+
*/
|
|
11
|
+
import type { CatalogModel } from "../../codex/catalog";
|
|
12
|
+
import { catalogModelSlug, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
|
|
13
|
+
import type { ExportModel } from "../../clients/config-export";
|
|
14
|
+
import { providerContextCap } from "../../providers/context-cap";
|
|
15
|
+
import { routedSlug, slugEquals } from "../../providers/slug-codec";
|
|
16
|
+
import type { OcxConfig } from "../../types";
|
|
17
|
+
import { fetchAllModels } from "./shared";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One row of the `/api/models` list. Routed rows spread a `CatalogModel`, so the shape is
|
|
21
|
+
* that model plus the identity/visibility fields this boundary computes for every row
|
|
22
|
+
* regardless of source. `disabled` is always present; the rest vary by row origin.
|
|
23
|
+
*/
|
|
24
|
+
export type ManagementModelRow = Partial<CatalogModel> & {
|
|
25
|
+
provider: string;
|
|
26
|
+
id: string;
|
|
27
|
+
namespaced: string;
|
|
28
|
+
disabled: boolean;
|
|
29
|
+
native?: boolean;
|
|
30
|
+
custom?: boolean;
|
|
31
|
+
customId?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The exact row list `/api/models` returns. Extracted so `/api/client-config` exports the
|
|
36
|
+
* models the GUI's Models tab shows — including this function's `disabled` computation,
|
|
37
|
+
* which the export core (src/clients/config-export.ts) deliberately does not perform.
|
|
38
|
+
*/
|
|
39
|
+
export async function listManagementModelRows(config: OcxConfig): Promise<ManagementModelRow[]> {
|
|
40
|
+
const models = await fetchAllModels(config);
|
|
41
|
+
const disabled = new Set(config.disabledModels ?? []);
|
|
42
|
+
// Native GPT passthrough rows lead (provider "openai", bare-slug namespaced ids): sourced
|
|
43
|
+
// from the static supported set so a disabled model stays listed and re-enableable.
|
|
44
|
+
const native: ManagementModelRow[] = nativeModelRows(config).map(row => ({
|
|
45
|
+
provider: "openai",
|
|
46
|
+
id: row.slug,
|
|
47
|
+
namespaced: row.slug,
|
|
48
|
+
disabled: row.disabled,
|
|
49
|
+
native: true,
|
|
50
|
+
...(row.contextWindow !== undefined ? { contextWindow: row.contextWindow } : {}),
|
|
51
|
+
}));
|
|
52
|
+
const customModels: ManagementModelRow[] = (config.customModels ?? []).map(cm => {
|
|
53
|
+
const namespaced = routedSlug(cm.provider, cm.modelId);
|
|
54
|
+
return {
|
|
55
|
+
provider: cm.provider,
|
|
56
|
+
id: cm.modelId,
|
|
57
|
+
namespaced,
|
|
58
|
+
disabled: [...disabled].some(stored => slugEquals(stored, cm.provider, cm.modelId)),
|
|
59
|
+
custom: true,
|
|
60
|
+
customId: cm.id,
|
|
61
|
+
displayName: cm.displayName,
|
|
62
|
+
...(cm.contextWindow ? { contextWindow: cm.contextWindow } : {}),
|
|
63
|
+
...(cm.inputModalities ? { inputModalities: cm.inputModalities } : {}),
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
const publicModels = uniqueCatalogModelsForPublicList(models);
|
|
67
|
+
const comboNamespaced = new Set(
|
|
68
|
+
publicModels.filter(model => model.provider === "combo").map(catalogModelSlug),
|
|
69
|
+
);
|
|
70
|
+
const visibleCustomModels = customModels.filter(model => !comboNamespaced.has(model.namespaced));
|
|
71
|
+
// Custom metadata wins when a physical live/static row resolves to the same Codex-facing
|
|
72
|
+
// slug, while a combo keeps the same precedence it has in routing and /v1/models.
|
|
73
|
+
const customNamespaced = new Set(visibleCustomModels.map(c => c.namespaced));
|
|
74
|
+
const dedupedRouted = publicModels.map((m): ManagementModelRow | null => {
|
|
75
|
+
// Codex-facing slug (one "/", slug-codec); disabledModels compares tolerate both forms.
|
|
76
|
+
const namespaced = catalogModelSlug(m);
|
|
77
|
+
if (m.provider !== "combo" && customNamespaced.has(namespaced)) return null;
|
|
78
|
+
const contextCap = providerContextCap(config, m.provider);
|
|
79
|
+
return {
|
|
80
|
+
...m,
|
|
81
|
+
namespaced,
|
|
82
|
+
disabled: [...disabled].some(stored => (
|
|
83
|
+
stored === namespaced || slugEquals(stored, m.provider, m.id)
|
|
84
|
+
)),
|
|
85
|
+
...(contextCap !== undefined ? { contextCap, contextCapped: m.contextCapped === true } : {}),
|
|
86
|
+
};
|
|
87
|
+
}).filter((row): row is ManagementModelRow => row !== null);
|
|
88
|
+
return [...native, ...dedupedRouted, ...visibleCustomModels];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `/api/models` row → the narrower input the client-config serializers accept. */
|
|
92
|
+
export function toExportModel(row: ManagementModelRow): ExportModel {
|
|
93
|
+
return {
|
|
94
|
+
namespaced: row.namespaced,
|
|
95
|
+
provider: row.provider,
|
|
96
|
+
id: row.id,
|
|
97
|
+
...(row.native ? { native: true } : {}),
|
|
98
|
+
...(row.displayName ? { displayName: row.displayName } : {}),
|
|
99
|
+
...(row.contextWindow !== undefined ? { contextWindow: row.contextWindow } : {}),
|
|
100
|
+
...(row.inputModalities ? { inputModalities: row.inputModalities } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Visible (non-disabled) rows as export models — the ONE loader both
|
|
106
|
+
* `/api/client-config` and the integration routes use, so the two can never
|
|
107
|
+
* disagree about which models a client is told about.
|
|
108
|
+
*
|
|
109
|
+
* The visibility filter lives HERE rather than at each call site: the export
|
|
110
|
+
* core serializes what it is given, so a model the user disabled in the Models
|
|
111
|
+
* tab is absent from `/v1/models` and exporting it would hand the client a
|
|
112
|
+
* selector the proxy refuses to route.
|
|
113
|
+
*/
|
|
114
|
+
export async function loadExportModels(config: OcxConfig): Promise<ExportModel[]> {
|
|
115
|
+
const rows = await listManagementModelRows(config);
|
|
116
|
+
return rows.filter(row => !row.disabled).map(toExportModel);
|
|
117
|
+
}
|