Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/cli/claude-desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
type DesktopProfile,
} from "../claude/desktop-profile";
import { writeDesktop3pConfig, type Desktop3pConfigMode, parseDesktop3pModeArgs } from "../claude/desktop-3p";
import { filterCatalogVisibleModels, visibleNativeSlugs } from "../codex/catalog";
import { filterCatalogVisibleModels, desktopVisibleNativeSlugs } from "../codex/catalog";
import { buildClaudeDesktopState, fetchAllModels } from "../server/management-api";
import { findLiveProxy } from "../server/proxy-liveness";

Expand Down Expand Up @@ -42,7 +42,7 @@ async function applyProfile(profile: DesktopProfile, mode: Desktop3pConfigMode):
}));
const result = writeDesktop3pConfig(
live?.port ?? config.port ?? 10100,
[...visibleNativeSlugs(config)],
[...desktopVisibleNativeSlugs(config)],
Comment thread
Wibias marked this conversation as resolved.
Resolved
routed,
config.apiKeys?.[0]?.key,
mode,
Expand Down
2 changes: 1 addition & 1 deletion src/codex/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Public surface preserved exactly; importers keep using "src/codex/catalog".
export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath, normalizeRoutedCatalogEntry, catalogModelSlug, filterSupportedNativeSlugs, catalogModelSupportsReasoningSummaries } from "./catalog/parsing";
export type { CatalogModel, MultiAgentMode } from "./catalog/parsing";
export { NATIVE_OPENAI_MODELS, nativeOpenAiContextWindow, disabledNativeSlugs, visibleNativeSlugs, nativeModelRows, applyNativeVisibility, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs } from "./catalog/metadata";
export { NATIVE_OPENAI_MODELS, nativeOpenAiContextWindow, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs } from "./catalog/metadata";
export { isSpawnableCodexCandidate, codexExecInvocation, loadBundledCodexCatalog, materializeBundledCodexCatalog, loadCatalogTemplate } from "./catalog/bundled";
export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, codexSupportedReasoningEfforts, clampedDefaultEffort, clampEntryToCodexSupportedEfforts, clampCatalogModelsToCodexSupport } from "./catalog/effort";
export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithJawcodeMetadata } from "./catalog/provider-fetch";
Expand Down
6 changes: 6 additions & 0 deletions src/codex/catalog/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ export function visibleNativeSlugs(config: Pick<OcxConfig, "disabledModels">): s
return nativeOpenAiSlugs().filter(slug => !disabled.has(slug));
}

/** Native slugs exposed to Claude Desktop show/export/apply (opt-out via claudeCode.desktopNativeModels). */
export function desktopVisibleNativeSlugs(config: Pick<OcxConfig, "claudeCode" | "disabledModels">): string[] {
if (config.claudeCode?.desktopNativeModels === false) return [];
return visibleNativeSlugs(config);
}

export function nativeModelRows(config: Pick<OcxConfig, "disabledModels">): Array<{ slug: string; disabled: boolean; contextWindow?: number }> {
const disabled = disabledNativeSlugs(config);
return NATIVE_OPENAI_MODELS.map(slug => {
Expand Down
6 changes: 3 additions & 3 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ export function startServer(port?: number) {
return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, config);
}
const goModels = await fetchAllModels(config);
const { applyNativeVisibility, buildCatalogEntries, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, nativeOpenAiSlugs, orderForSubagents, filterCatalogVisibleModels, uniqueCatalogModelsForRawPublicList, visibleNativeSlugs } = await import("../codex/catalog");
const { applyNativeVisibility, buildCatalogEntries, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, nativeOpenAiSlugs, orderForSubagents, filterCatalogVisibleModels, uniqueCatalogModelsForRawPublicList, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
const nativeSlugs = nativeOpenAiSlugs();
const goEnabled = filterCatalogVisibleModels(goModels, config);
const goOrdered = orderForSubagents(goEnabled, config.subagentModels);
Expand All @@ -424,7 +424,7 @@ export function startServer(port?: number) {
if (config.claudeCode?.enabled === false) return jsonResponse({ data: [] }, 200, req, config);
// Build Desktop 3P registry so inbound alias resolution works for subsequent requests.
buildDesktop3pRegistry(
[...visibleNativeSlugs(config)],
[...desktopVisibleNativeSlugs(config)],
goOrdered.map(m => ({ provider: m.provider, id: m.id, contextWindow: m.contextWindow })),
config.claudeCode?.desktopProfile,
);
Expand All @@ -441,7 +441,7 @@ export function startServer(port?: number) {
: idsParam === "desktop"
? "desktop3p" as const
: (/^claude-code\//i.test(req.headers.get("user-agent") ?? "") ? "readable" as const : "desktop3p" as const);
const data = buildAnthropicModelInfos([...visibleNativeSlugs(config)], goOrdered, resolveAutoContext(config.claudeCode), idStyle, activeDesktop3pAlias);
const data = buildAnthropicModelInfos([...desktopVisibleNativeSlugs(config)], goOrdered, resolveAutoContext(config.claudeCode), idStyle, activeDesktop3pAlias);
return jsonResponse({ data }, 200, req, config);
}
if (url.searchParams.has("client_version")) {
Expand Down
8 changes: 4 additions & 4 deletions src/server/management/agent-settings-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
if (config.claudeCode?.desktopAutoApply === false) return;
if (!config.claudeCode?.desktopProfile) return;
const { writeDesktop3pConfig } = await import("../../claude/desktop-3p");
const { visibleNativeSlugs, filterCatalogVisibleModels } = await import("../../codex/catalog");
const { filterCatalogVisibleModels, desktopVisibleNativeSlugs } = await import("../../codex/catalog");
const allModels = await fetchAllModels(config);
const routed = filterCatalogVisibleModels(allModels, config).map(m => ({ provider: m.provider, id: m.id, contextWindow: m.contextWindow }));
const result = writeDesktop3pConfig(
config.port ?? 10100,
[...visibleNativeSlugs(config)],
[...desktopVisibleNativeSlugs(config)],
routed,
config.apiKeys?.[0]?.key,
"static",
Expand Down Expand Up @@ -533,7 +533,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
config.claudeCode = { ...(config.claudeCode ?? {}), desktopProfile: state.profile };
saveConfigPreservingClaudeCode(config);
const { writeDesktop3pConfig } = await import("../../claude/desktop-3p");
const { visibleNativeSlugs } = await import("../../codex/catalog");
const { desktopVisibleNativeSlugs } = await import("../../codex/catalog");
const routed = state.models
.filter(model => model.available && !model.route.startsWith("native/"))
.map(model => {
Expand All @@ -542,7 +542,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
});
const result = writeDesktop3pConfig(
Number(url.port) || config.port,
[...visibleNativeSlugs(config)],
[...desktopVisibleNativeSlugs(config)],
routed,
config.apiKeys?.[0]?.key,
"static",
Expand Down
19 changes: 16 additions & 3 deletions src/server/management/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,14 @@ export function stripRegistryOnlyStaticHeaders(name: string, provider: OcxProvid

/** Shared Desktop profile DTO builder for the management API and CLI. */
export async function buildClaudeDesktopState(config: OcxConfig, stored?: OcxClaudeDesktopProfile) {
const { filterCatalogVisibleModels, nativeOpenAiContextWindow, visibleNativeSlugs } = await import("../../codex/catalog");
const { filterCatalogVisibleModels, nativeOpenAiContextWindow, desktopVisibleNativeSlugs } = await import("../../codex/catalog");
const { DESKTOP_SUPPORTS_1M_THRESHOLD } = await import("../../claude/desktop-3p");
const { reconcileDesktopProfile, renderDesktopProfile } = await import("../../claude/desktop-profile");
const routed = filterCatalogVisibleModels(await fetchAllModels(config), config);
const profileModels: DesktopProfileModel[] = [
// Native rows carry their real context window from the same accessor the Grok sync
// uses — otherwise Sol's 372k and gpt-5.5's 272k render as blank on Desktop.
...visibleNativeSlugs(config).map(id => {
...desktopVisibleNativeSlugs(config).map(id => {
const contextWindow = nativeOpenAiContextWindow(id);
return { route: `native/${id}`, label: `${id} (native)`,
...(contextWindow !== undefined ? { contextWindow } : {}) };
Expand All @@ -233,6 +233,19 @@ export async function buildClaudeDesktopState(config: OcxConfig, stored?: OcxCla
})),
];
const profile = reconcileDesktopProfile(stored ?? config.claudeCode?.desktopProfile, profileModels);
if (config.claudeCode?.desktopNativeModels === false) {
for (const route of Object.keys(profile.assignments)) {
if (route.startsWith("native/")) delete profile.assignments[route];
}
for (const family of ["opus", "fable", "sonnet", "haiku"] as const) {
const current = profile.defaults[family];
if (current?.startsWith("native/")) {
profile.defaults[family] = Object.keys(profile.assignments)
.filter(route => profile.assignments[route]?.family === family)
.sort()[0] ?? null;
}
}
}
const available = new Set(profileModels.map(model => model.route));
const modelByRoute = new Map(profileModels.map(model => [model.route, model]));
// Effort support: routed models with a non-empty reasoningEfforts ladder support effort;
Expand All @@ -241,7 +254,7 @@ export async function buildClaudeDesktopState(config: OcxConfig, stored?: OcxCla
for (const m of routed) {
effortByRoute.set(`${m.provider}/${m.id}`, Array.isArray(m.reasoningEfforts) && m.reasoningEfforts.length > 0);
}
for (const id of visibleNativeSlugs(config)) {
for (const id of desktopVisibleNativeSlugs(config)) {
effortByRoute.set(`native/${id}`, true);
}
const models = Object.keys(profile.assignments).sort().map(route => ({
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,11 @@ export interface OcxClaudeCodeConfig {
desktopProfile?: OcxClaudeDesktopProfile;
/** Auto-reconcile Desktop 3P config when provider catalog changes. Default: enabled. */
desktopAutoApply?: boolean;
/**
* When false, omit `native/*` rows from Claude Desktop show/export/apply. Default: enabled.
* Routing-sidecar alias decoding is unchanged — only the Desktop model list writer.
*/
desktopNativeModels?: boolean;
}

export type OcxClaudeDesktopFamily = "opus" | "fable" | "sonnet" | "haiku";
Expand Down
25 changes: 25 additions & 0 deletions tests/claude-desktop-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,31 @@ test("import rejects invalid profiles without replacing saved state", async () =
}
});

test("desktopNativeModels:false omits native/* from show and exported profile", async () => {
saveConfig({
port: 10100,
defaultProvider: "mock",
providers: {
mock: { adapter: "openai-chat", baseUrl: "http://127.0.0.1:1/v1", apiKey: "k", allowPrivateNetwork: true, models: ["test-model"] },
},
claudeCode: { desktopNativeModels: false },
} as OcxConfig);
const log = spyOn(console, "log").mockImplementation(() => {});
try {
expect(await handleClaudeDesktopCommand(["show", "--json"])).toBe(0);
const state = JSON.parse(String(log.mock.calls.at(-1)?.[0]));
expect(state.models.every((model: { route: string }) => !model.route.startsWith("native/"))).toBe(true);
expect(Object.keys(state.profile.assignments).every((route: string) => !route.startsWith("native/"))).toBe(true);

const target = join(dir, "desktop-profile.json");
expect(await handleClaudeDesktopCommand(["export", target])).toBe(0);
const exported = JSON.parse(readFileSync(target, "utf8"));
expect(Object.keys(exported.assignments).every((route: string) => !route.startsWith("native/"))).toBe(true);
} finally {
log.mockRestore();
}
});

test("no-arg and legacy mode flags apply Desktop config", async () => {
const log = spyOn(console, "log").mockImplementation(() => {});
const error = spyOn(console, "error").mockImplementation(() => {});
Expand Down
17 changes: 17 additions & 0 deletions tests/grok-management-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,23 @@ test("GET /api/grok includes candidates and the saved exclusion list", async ()
}
});

test("GET /api/grok keeps native candidates when claudeCode.desktopNativeModels is false", async () => {
const config = loadConfig();
config.claudeCode = { desktopNativeModels: false };
saveConfig(config);
const server = startServer(0);
try {
const res = await fetch(new URL("/api/grok", server.url));
expect(res.status).toBe(200);
const body = await res.json() as {
candidates: Array<{ id: string; native: boolean }>;
};
expect(body.candidates.some(c => c.native)).toBe(true);
} finally {
server.stop(true);
}
});

test("POST /api/grok/apply reports no-grok-home as a policy skip, not an error", async () => {
const server = startServer(0);
try {
Expand Down
Loading