@bitkyc08/opencodex 2.62.0-preview.20260923 → 2.63.0-preview.20260923
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/gui/dist/assets/App-EJxiUFMq.js +50 -0
- package/gui/dist/assets/{Tray-DdvPBVLi.js → Tray-B03pW-Uf.js} +1 -1
- package/gui/dist/assets/index-BmJwNBHL.js +86 -0
- package/gui/dist/assets/index-DdDunwDb.css +1 -0
- package/gui/dist/assets/{usage-companion-chart-BIhvlbQW.js → usage-companion-chart-IftE60UK.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/cursor/catalog.ts +15 -0
- package/src/adapters/cursor/effort-map.ts +7 -0
- package/src/adapters/cursor/envelope-echo.ts +51 -25
- package/src/adapters/cursor/protobuf-request.ts +39 -13
- package/src/adapters/cursor.ts +52 -4
- package/src/adapters/devin/cloud-direct/chat.ts +50 -16
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +4 -1
- package/src/adapters/devin/live-models.ts +7 -0
- package/src/adapters/devin.ts +7 -5
- package/src/adapters/kiro/reasoning.ts +5 -0
- package/src/adapters/openai-responses/passthrough.ts +4 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +5 -3
- package/src/claude/desktop-gateway-state.ts +7 -2
- package/src/claude/desktop-policy.ts +105 -1
- package/src/cli/config-command.ts +22 -7
- package/src/cli/doctor.ts +14 -4
- package/src/codex/catalog/effort.ts +35 -4
- package/src/codex/catalog/metadata.ts +33 -5
- package/src/codex/catalog/native-models.ts +43 -2
- package/src/codex/catalog/pinned-models.ts +37 -0
- package/src/codex/catalog-auto-refresh.ts +6 -0
- package/src/codex/data/roster-pinned-models.json +359 -0
- package/src/codex/data/upstream-models.json +365 -271
- package/src/codex/inject/provider-table.ts +108 -0
- package/src/codex/inject/remove.ts +2 -114
- package/src/codex/model-entitlements.ts +14 -10
- package/src/codex/subagent-defaults.ts +2 -109
- package/src/codex/toml-source-lines.ts +112 -0
- package/src/config/live-reconcile.ts +145 -27
- package/src/config/load-degrade.ts +3 -4
- package/src/config.ts +2 -2
- package/src/generated/compatibility-version.json +83 -67
- package/src/generated/model-metadata.ts +5 -5
- package/src/lab/artifacts/sanitize.ts +60 -19
- package/src/lib/app-owned-memory-stores.ts +7 -0
- package/src/lib/app-owned-memory.ts +13 -2
- package/src/lib/errors.ts +6 -4
- package/src/lib/upstream-retry.ts +35 -5
- package/src/oauth/devin.ts +43 -37
- package/src/providers/codebuddy-models.ts +11 -0
- package/src/providers/kiro-models.ts +9 -0
- package/src/providers/quota/vendor-probes-oauth.ts +9 -2
- package/src/providers/registry/entries-core.ts +19 -8
- package/src/providers/registry/entries-extended.ts +4 -1
- package/src/providers/registry/model-seeds.ts +22 -2
- package/src/responses/bridge-search-replay-cache.ts +20 -10
- package/src/responses/plaintext-v2-agent-messages.ts +10 -1
- package/src/routing/identity-domains.ts +22 -15
- package/src/server/index/websocket-handler.ts +22 -2
- package/src/server/management/agent-settings-routes.ts +19 -10
- package/src/server/management/context.ts +4 -2
- package/src/server/responses/codex-ws-exchange.ts +24 -8
- package/src/server/responses/core-codex-account.ts +4 -0
- package/src/server/responses/core-combo-failure.ts +16 -9
- package/src/server/responses/core-combo.ts +7 -3
- package/src/server/responses/core-options.ts +6 -0
- package/src/server/responses/native-injection-replay.ts +13 -1
- package/src/server/responses/native-injection.ts +66 -7
- package/src/server/responses/native-response-control.ts +6 -2
- package/src/server/responses/native-steering-replay.ts +60 -0
- package/src/server/responses/native-steering.ts +9 -1
- package/src/server/responses/passthrough-delivery.ts +3 -4
- package/src/server/responses/passthrough-dispatch.ts +7 -0
- package/src/server/responses/request-prepare.ts +12 -0
- package/src/server/responses/request-transport.ts +5 -2
- package/src/server/responses/ws-upstream.ts +1 -1
- package/src/server/ws-bridge.ts +17 -1
- package/src/types/request.ts +5 -0
- package/src/usage/expected-prices.ts +38 -8
- package/src/web-search/executor.ts +38 -13
- package/gui/dist/assets/App-CANWata-.js +0 -50
- package/gui/dist/assets/index-BIS5HXEP.js +0 -86
- package/gui/dist/assets/index-_bpvxJu0.css +0 -1
|
@@ -21,6 +21,9 @@ export const DEVIN_STATIC_MODELS = [
|
|
|
21
21
|
"gpt-5-6-sol",
|
|
22
22
|
"gpt-5-6-luna",
|
|
23
23
|
"gpt-5-6-terra",
|
|
24
|
+
// 260923 preemptive: GPT-6 Sol and Luna (OpenAI announced 2026-09-22) added ahead of this provider's own catalog; mirrors the GPT-5.6 Sol/Luna rows.
|
|
25
|
+
"gpt-6-sol",
|
|
26
|
+
"gpt-6-luna",
|
|
24
27
|
"claude-opus-4-8",
|
|
25
28
|
"claude-fable-5-1",
|
|
26
29
|
"claude-sonnet-5",
|
|
@@ -55,7 +58,11 @@ export const DEVIN_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
|
55
58
|
"gpt-5-6-luna": 1_000_000,
|
|
56
59
|
"gpt-5-6-terra": 1_000_000,
|
|
57
60
|
"gpt-6-astra": 1_000_000,
|
|
61
|
+
"gpt-6-sol": 1_000_000,
|
|
62
|
+
"gpt-6-luna": 1_000_000,
|
|
58
63
|
"claude-opus-4-8": 1_000_000,
|
|
64
|
+
// 260923: read from the live catalog (devin/claude-opus-5-5 context_length 1_000_000).
|
|
65
|
+
"claude-opus-5-5": 1_000_000,
|
|
59
66
|
"claude-opus-5": 1_000_000,
|
|
60
67
|
"claude-fable-5-1": 1_000_000,
|
|
61
68
|
"claude-sonnet-5": 1_000_000,
|
package/src/adapters/devin.ts
CHANGED
|
@@ -612,7 +612,7 @@ export function createDevinAdapter(
|
|
|
612
612
|
// The signed-in account's tenant decides the host, not the static registry
|
|
613
613
|
// entry: an EU or FedStart account that used provider.baseUrl would send
|
|
614
614
|
// every RPC to the US server it is not provisioned on.
|
|
615
|
-
const host = resolveDevinApiServer(provider.baseUrl, credentialProviderId);
|
|
615
|
+
const host = resolveDevinApiServer(provider.baseUrl, credentialProviderId, apiKey);
|
|
616
616
|
// One catalog read per turn serves model-UID resolution, the input
|
|
617
617
|
// ceiling, and the chat pre-flight inside streamChatEvents. Failures are
|
|
618
618
|
// not cached, so a second read would only pay another fetch timeout on
|
|
@@ -642,10 +642,11 @@ export function createDevinAdapter(
|
|
|
642
642
|
const maxOutputTokens = resolveDevinMaxOutputTokens(
|
|
643
643
|
provider, modelUid, parsed.options.maxOutputTokens,
|
|
644
644
|
);
|
|
645
|
-
//
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
//
|
|
645
|
+
// An admitted HTTP turn owns globally shared capacity until this call
|
|
646
|
+
// emits. Never retain that capacity while waiting out a provider 429:
|
|
647
|
+
// preserve the typed reset delay in generated diagnostic wording,
|
|
648
|
+
// never the raw trailer text that may reflect a credential. The
|
|
649
|
+
// refusal returns immediately so the caller can release its slot.
|
|
649
650
|
for await (const event of streamChatEventsWithResetRetry({
|
|
650
651
|
apiKey,
|
|
651
652
|
apiServerUrl: host,
|
|
@@ -664,6 +665,7 @@ export function createDevinAdapter(
|
|
|
664
665
|
},
|
|
665
666
|
signal: incoming.abortSignal,
|
|
666
667
|
}, {
|
|
668
|
+
maxWaitMs: 0,
|
|
667
669
|
execution: {
|
|
668
670
|
executor: incoming.providerFetch,
|
|
669
671
|
sendBudget: incoming.sendBudget,
|
|
@@ -23,7 +23,12 @@ export const KIRO_NATIVE_EFFORT_FIELDS: Record<string, "reasoning" | "output_con
|
|
|
23
23
|
"gpt-5.6-sol": "reasoning",
|
|
24
24
|
"gpt-5.6-terra": "reasoning",
|
|
25
25
|
"gpt-5.6-luna": "reasoning",
|
|
26
|
+
// 260923 preemptive (see kiro-models.ts): same native field as the GPT-5.6 family.
|
|
27
|
+
"gpt-6-sol": "reasoning",
|
|
28
|
+
"gpt-6-luna": "reasoning",
|
|
26
29
|
"claude-opus-5": "output_config",
|
|
30
|
+
// 260923 preemptive (see kiro-models.ts): same Claude-specific field as Opus 5.
|
|
31
|
+
"claude-opus-5.5": "output_config",
|
|
27
32
|
};
|
|
28
33
|
|
|
29
34
|
export const KIRO_NATIVE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
@@ -329,11 +329,11 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
329
329
|
outBody = stripUnsupportedReasoningSummaryDelivery(outBody, parsed.modelId);
|
|
330
330
|
// #4587: on a bridged provider, hand the destination back the search call and result the
|
|
331
331
|
// proxy executed on its behalf, in place of the hosted cell the caller replays. Scoped to
|
|
332
|
-
//
|
|
333
|
-
// computes no identity and keeps the body reference it already
|
|
334
|
-
// query backfill
|
|
332
|
+
// its exact conversation and serving identity and recorded by the bridge itself, so a
|
|
333
|
+
// provider without the opt-in computes no identity and keeps the body reference it already
|
|
334
|
+
// had. This runs before query backfill because a restored cell is no longer one to repair.
|
|
335
335
|
if (provider.webSearchBridge?.enabled === true) {
|
|
336
|
-
outBody = restoreBridgedWebSearchCalls(outBody, bridgeSearchReplayScope(
|
|
336
|
+
outBody = restoreBridgedWebSearchCalls(outBody, bridgeSearchReplayScope(parsed._reasoningReplayScope));
|
|
337
337
|
}
|
|
338
338
|
// Repair stored history from before the bridge emitted both keys, in either
|
|
339
339
|
// direction: a conversation that already recorded a web_search_call replays it
|
|
@@ -289,9 +289,11 @@ export function backfillWebSearchQueries(body: unknown): unknown {
|
|
|
289
289
|
* - It never restores a call id the body already carries. If the history somehow holds that
|
|
290
290
|
* `function_call` too, emitting a second one would be a duplicate the upstream must reject.
|
|
291
291
|
*
|
|
292
|
-
* Entries are scoped to the
|
|
293
|
-
*
|
|
294
|
-
*
|
|
292
|
+
* Entries are scoped to the caller principal, conversation and exact serving identity, so a
|
|
293
|
+
* history replayed by another caller or against a different provider, model, destination or
|
|
294
|
+
* credential cannot resurrect a call that pairing never made. Callers pass `undefined` for any
|
|
295
|
+
* provider without the bridge armed and for a caller with no principal, and the common path then
|
|
296
|
+
* returns the original reference.
|
|
295
297
|
*/
|
|
296
298
|
export function restoreBridgedWebSearchCalls(body: unknown, destinationScope: string | undefined): unknown {
|
|
297
299
|
if (destinationScope === undefined) return body;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mutatePersistedConfig } from "../config";
|
|
1
|
+
import { adoptPersistedClaudeCode, mutatePersistedConfig } from "../config";
|
|
2
2
|
import type { OcxConfig } from "../types";
|
|
3
3
|
import { emptyDesktopProfile, type DesktopProfile } from "./desktop-profile";
|
|
4
4
|
|
|
@@ -30,9 +30,14 @@ export function persistCommittedDesktopGateway(
|
|
|
30
30
|
try {
|
|
31
31
|
const outcome = mutatePersistedConfig(current => {
|
|
32
32
|
recordCommittedDesktopGateway(current, profile, fingerprint, appliedAt);
|
|
33
|
-
return { changed: true, value:
|
|
33
|
+
return { changed: true, value: structuredClone(current.claudeCode) };
|
|
34
34
|
});
|
|
35
35
|
if (outcome.status === "unavailable") return { ok: false, reason: outcome.reason };
|
|
36
|
+
adoptPersistedClaudeCode(snapshot, outcome.value);
|
|
37
|
+
// The mode/profile pair IS the committed transaction, not mergeable state.
|
|
38
|
+
// Without an armed baseline the three-way adopt cannot prove the live leaves
|
|
39
|
+
// unchanged and keeps a stale live desktopMode over the bytes just saved, so
|
|
40
|
+
// pin both leaves to the committed subtree after the disjoint-leaf merge.
|
|
36
41
|
recordCommittedDesktopGateway(snapshot, profile, fingerprint, appliedAt);
|
|
37
42
|
return { ok: true };
|
|
38
43
|
} catch {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Read-only, privacy-safe Windows policy diagnosis for Claude Desktop 3P. */
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { execFile, spawnSync } from "node:child_process";
|
|
3
3
|
import { win32 } from "node:path";
|
|
4
4
|
import { resolveTrustedWindowsSystemDirectory } from "../lib/windows-elevation";
|
|
5
5
|
import { decodeWindowsTextBytes } from "../lib/windows-text";
|
|
@@ -23,6 +23,11 @@ export type ClaudeDesktopPolicyProbeRunner = (
|
|
|
23
23
|
args: readonly string[],
|
|
24
24
|
) => ClaudeDesktopPolicyProbeResult;
|
|
25
25
|
|
|
26
|
+
export type ClaudeDesktopPolicyAsyncProbeRunner = (
|
|
27
|
+
file: string,
|
|
28
|
+
args: readonly string[],
|
|
29
|
+
) => Promise<ClaudeDesktopPolicyProbeResult>;
|
|
30
|
+
|
|
26
31
|
export interface ClaudeDesktopPolicyProbeOptions {
|
|
27
32
|
readonly platform?: NodeJS.Platform;
|
|
28
33
|
readonly run?: ClaudeDesktopPolicyProbeRunner;
|
|
@@ -54,6 +59,38 @@ const defaultPolicyProbeRunner: ClaudeDesktopPolicyProbeRunner = (file, args) =>
|
|
|
54
59
|
};
|
|
55
60
|
};
|
|
56
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Translates an `execFile` callback into the probe contract. Exit codes arrive
|
|
64
|
+
* as numeric `error.code`; spawn failures carry a string errno; a timeout kill
|
|
65
|
+
* surfaces as `killed`/`SIGTERM` rather than `ETIMEDOUT`. A killed child is a
|
|
66
|
+
* TIMEOUT, never a spawn failure — the process started fine and ran out of time,
|
|
67
|
+
* so `spawnFailed` stays false or diagnostics conflate "did not start" with
|
|
68
|
+
* "ran too long".
|
|
69
|
+
*/
|
|
70
|
+
export function classifyExecFileProbeResult(
|
|
71
|
+
error: (Error & { readonly code?: number | string; readonly killed?: boolean }) | null,
|
|
72
|
+
stdout: Uint8Array | undefined,
|
|
73
|
+
): ClaudeDesktopPolicyProbeResult {
|
|
74
|
+
const errorCode = error?.code;
|
|
75
|
+
return {
|
|
76
|
+
status: error === null ? 0 : typeof errorCode === "number" ? errorCode : null,
|
|
77
|
+
stdout: stdout === undefined ? "" : decodeWindowsTextBytes(stdout),
|
|
78
|
+
timedOut: errorCode === "ETIMEDOUT" || error?.killed === true,
|
|
79
|
+
spawnFailed: error !== null && error.killed !== true && typeof errorCode !== "number" && errorCode !== "ETIMEDOUT",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const defaultAsyncPolicyProbeRunner: ClaudeDesktopPolicyAsyncProbeRunner = (file, args) => new Promise((resolve) => {
|
|
84
|
+
execFile(file, [...args], {
|
|
85
|
+
encoding: "buffer",
|
|
86
|
+
maxBuffer: 64 * 1024,
|
|
87
|
+
timeout: POLICY_PROBE_TIMEOUT_MS,
|
|
88
|
+
windowsHide: true,
|
|
89
|
+
}, (error, stdout) => {
|
|
90
|
+
resolve(classifyExecFileProbeResult(error, stdout));
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
57
94
|
function usable(result: ClaudeDesktopPolicyProbeResult): boolean {
|
|
58
95
|
return !result.timedOut && !result.spawnFailed && result.status !== null;
|
|
59
96
|
}
|
|
@@ -108,6 +145,73 @@ export function probeClaudeDesktopPolicy(
|
|
|
108
145
|
return parentListsPolicyKey(parent.stdout) ? "unknown" : "absent";
|
|
109
146
|
}
|
|
110
147
|
|
|
148
|
+
/** Non-blocking variant for the long-lived server request path. */
|
|
149
|
+
export async function probeClaudeDesktopPolicyAsync(
|
|
150
|
+
options: Omit<ClaudeDesktopPolicyProbeOptions, "run"> & { readonly run?: ClaudeDesktopPolicyAsyncProbeRunner } = {},
|
|
151
|
+
): Promise<ClaudeDesktopPolicyState> {
|
|
152
|
+
const platform = options.platform ?? process.platform;
|
|
153
|
+
if (platform !== "win32") return "not_applicable";
|
|
154
|
+
|
|
155
|
+
let regExe: string;
|
|
156
|
+
try {
|
|
157
|
+
const systemDirectory = (options.resolveSystemDirectory ?? resolveTrustedWindowsSystemDirectory)();
|
|
158
|
+
regExe = win32.join(systemDirectory, "reg.exe");
|
|
159
|
+
} catch {
|
|
160
|
+
return "unknown";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const run = options.run ?? defaultAsyncPolicyProbeRunner;
|
|
164
|
+
try {
|
|
165
|
+
const policy = await run(regExe, ["query", CLAUDE_POLICY_KEY, "/reg:64"]);
|
|
166
|
+
if (!usable(policy)) return "unknown";
|
|
167
|
+
if (policy.status === 0) return "present";
|
|
168
|
+
if (policy.status !== 1) return "unknown";
|
|
169
|
+
|
|
170
|
+
const parent = await run(regExe, ["query", CLAUDE_POLICY_PARENT_KEY, "/reg:64"]);
|
|
171
|
+
if (!usable(parent) || parent.status !== 0) return "unknown";
|
|
172
|
+
return parentListsPolicyKey(parent.stdout) ? "unknown" : "absent";
|
|
173
|
+
} catch {
|
|
174
|
+
return "unknown";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const POLICY_CACHE_TTL_MS = 30_000;
|
|
179
|
+
|
|
180
|
+
export function createCachedClaudeDesktopPolicyProbe(
|
|
181
|
+
probe: () => Promise<ClaudeDesktopPolicyState>,
|
|
182
|
+
ttlMs = POLICY_CACHE_TTL_MS,
|
|
183
|
+
now = performance.now,
|
|
184
|
+
): () => Promise<ClaudeDesktopPolicyState> {
|
|
185
|
+
let cached: { state: ClaudeDesktopPolicyState; expiresAt: number } | undefined;
|
|
186
|
+
let refresh: Promise<ClaudeDesktopPolicyState> | undefined;
|
|
187
|
+
return () => {
|
|
188
|
+
const currentTime = now();
|
|
189
|
+
if (cached && cached.expiresAt > currentTime) return Promise.resolve(cached.state);
|
|
190
|
+
if (refresh) return refresh;
|
|
191
|
+
refresh = probe().then((state) => {
|
|
192
|
+
cached = { state, expiresAt: now() + ttlMs };
|
|
193
|
+
return state;
|
|
194
|
+
}).finally(() => {
|
|
195
|
+
refresh = undefined;
|
|
196
|
+
});
|
|
197
|
+
return refresh;
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const cachedProductionProbe = createCachedClaudeDesktopPolicyProbe(
|
|
202
|
+
() => probeClaudeDesktopPolicyAsync(),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
/** Coalesces status polling and bounds registry refreshes to one per cache interval. */
|
|
206
|
+
export function getCachedClaudeDesktopPolicy(
|
|
207
|
+
options: Omit<ClaudeDesktopPolicyProbeOptions, "run"> = {},
|
|
208
|
+
): Promise<ClaudeDesktopPolicyState> {
|
|
209
|
+
const cacheable = options.resolveSystemDirectory === undefined
|
|
210
|
+
&& (options.platform === undefined || options.platform === process.platform);
|
|
211
|
+
if (cacheable) return cachedProductionProbe();
|
|
212
|
+
return probeClaudeDesktopPolicyAsync(options);
|
|
213
|
+
}
|
|
214
|
+
|
|
111
215
|
/** State-only health projection shared by CLI, apply, and management status. */
|
|
112
216
|
export function claudeDesktopPolicyHealth(
|
|
113
217
|
state: ClaudeDesktopPolicyState,
|
|
@@ -5,6 +5,7 @@ import { VISION_REASONING_EFFORTS, isVisionReasoningEffort } from "../reasoning-
|
|
|
5
5
|
import type { OcxConfig } from "../types";
|
|
6
6
|
import { normalizeVisionReasoningForModel } from "../vision/reasoning";
|
|
7
7
|
import type { ServiceApiTokenState } from "../lib/service-secrets";
|
|
8
|
+
import { redactUrlForLog } from "../lib/redact";
|
|
8
9
|
import { CliUsageError, printData, rejectArgs, runCliAction, takeFlag } from "./runtime-api";
|
|
9
10
|
|
|
10
11
|
const USAGE = `Usage:
|
|
@@ -17,12 +18,13 @@ const USAGE = `Usage:
|
|
|
17
18
|
ocx config import <path|-> --yes [--json]`;
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
|
-
* Keys whose VALUE is a credential and must never be printed
|
|
21
|
+
* Keys whose VALUE is a credential and must never be printed by display commands.
|
|
22
|
+
* `config export` writes the raw config so an export can restore credentials; it does
|
|
23
|
+
* not call `redact`.
|
|
21
24
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* so `ocx config show` printed it and `config export` wrote it to disk in the clear.
|
|
25
|
+
* URL-valued credentials must be named explicitly: `webhookUrl` matches none of the
|
|
26
|
+
* other patterns, and a proxy URL's userinfo is handled by the `proxy` branch in
|
|
27
|
+
* `redact` rather than by masking the whole value.
|
|
26
28
|
*/
|
|
27
29
|
const SECRET_KEYS = /^(apiKey|key|accessToken|refreshToken|idToken|token|password|clientSecret|webhookUrl)$/i;
|
|
28
30
|
const BLOCKED_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
@@ -97,6 +99,19 @@ async function readRemoteHubConfigNote(config: OcxConfig): Promise<ReturnType<ty
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
function redact(value: unknown, key = ""): unknown {
|
|
102
|
+
if (key === "proxy" && typeof value === "string") {
|
|
103
|
+
// "direct" and credential-less proxy URLs carry no secret and stay readable; only a
|
|
104
|
+
// URL with userinfo is masked, and then only its credentials — host and port stay
|
|
105
|
+
// visible so the output still says WHERE traffic goes. A non-URL value that is not
|
|
106
|
+
// "direct" cannot be proven credential-free, so it is masked whole.
|
|
107
|
+
if (!value || value === "direct") return value;
|
|
108
|
+
try {
|
|
109
|
+
const parsed = new URL(value);
|
|
110
|
+
return parsed.username || parsed.password ? redactUrlForLog(value) : value;
|
|
111
|
+
} catch {
|
|
112
|
+
return "********";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
100
115
|
if (SECRET_KEYS.test(key) && typeof value === "string") return value ? "********" : value;
|
|
101
116
|
// `client.priorCatalog` is the base64 catalog snapshot connect took before overwriting the
|
|
102
117
|
// local one — up to 64 MB of it (src/config.ts). Printed in full it buried `runtimeRole` and
|
|
@@ -211,7 +226,7 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
|
|
|
211
226
|
const path = args.shift();
|
|
212
227
|
if (!path) throw new CliUsageError("config path is required", USAGE);
|
|
213
228
|
rejectArgs(args, USAGE);
|
|
214
|
-
const value = redact(getPath(readConfigDiagnostics().config, path), path
|
|
229
|
+
const value = redact(getPath(readConfigDiagnostics().config, path), pathSegments(path).at(-1));
|
|
215
230
|
if (wantsJson || typeof value === "object") console.log(JSON.stringify(value, null, 2));
|
|
216
231
|
else console.log(String(value));
|
|
217
232
|
return;
|
|
@@ -257,7 +272,7 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
|
|
|
257
272
|
? "config changed while applying this update; retry"
|
|
258
273
|
: `config is ${outcome.reason}`);
|
|
259
274
|
}
|
|
260
|
-
printData({ ok: true, path, value: redact(savedValue, path
|
|
275
|
+
printData({ ok: true, path, value: redact(savedValue, pathSegments(path).at(-1)) }, wantsJson,
|
|
261
276
|
[`${action === "unset" ? "Unset" : "Set"} ${path}.`]);
|
|
262
277
|
return;
|
|
263
278
|
}
|
package/src/cli/doctor.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { getConfigDir, getConfigPath, readConfigDiagnostics } from "../config";
|
|
|
14
14
|
import { readPid } from "../config/process-state";
|
|
15
15
|
import { probeUncleanExitState } from "./status";
|
|
16
16
|
import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
|
|
17
|
+
import { directLocalHttpFetch } from "../server/direct-local-http";
|
|
17
18
|
import { BUN_RUNTIME_SOURCES } from "../lib/bun-runtime";
|
|
18
19
|
import type { BunRuntimeSource } from "../lib/bun-runtime";
|
|
19
20
|
import { maskAccountId } from "../lib/privacy";
|
|
@@ -1073,12 +1074,15 @@ export interface DefaultModelExposure {
|
|
|
1073
1074
|
|
|
1074
1075
|
/** Exactly the catalog's own `RawEntry` shape, so an on-disk row needs no conversion. */
|
|
1075
1076
|
type CatalogVisibilityRow = Record<string, unknown>;
|
|
1077
|
+
type ExposedModelsFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
1078
|
+
const EXPOSED_MODELS_MAX_ROWS = 10_000;
|
|
1079
|
+
const EXPOSED_MODEL_ID_MAX_LENGTH = 1_024;
|
|
1076
1080
|
|
|
1077
1081
|
export interface DefaultModelExposureDeps {
|
|
1078
1082
|
readConfiguredModelFn?: () => string | null;
|
|
1079
1083
|
/** The live proxy doctor already resolved, or null/absent when none is running. */
|
|
1080
1084
|
live?: LiveProxy | null;
|
|
1081
|
-
fetchFn?:
|
|
1085
|
+
fetchFn?: ExposedModelsFetch;
|
|
1082
1086
|
readCatalogModelsFn?: () => readonly CatalogVisibilityRow[] | null;
|
|
1083
1087
|
}
|
|
1084
1088
|
|
|
@@ -1091,18 +1095,24 @@ export interface DefaultModelExposureDeps {
|
|
|
1091
1095
|
* data-plane admission on a non-loopback bind (`isApiAuthRequired`), and doctor deliberately
|
|
1092
1096
|
* holds no data-plane key, so a remote-bound proxy always falls through to the catalog.
|
|
1093
1097
|
*/
|
|
1094
|
-
async function fetchExposedModelIds(live: LiveProxy, fetchFn:
|
|
1098
|
+
async function fetchExposedModelIds(live: LiveProxy, fetchFn: ExposedModelsFetch): Promise<Set<string> | null> {
|
|
1095
1099
|
try {
|
|
1100
|
+
// directLocalHttpFetch never follows redirects and aborts past its byte cap, so the
|
|
1101
|
+
// unbounded-body and redirect cases are covered below the JSON parse, not by options here.
|
|
1096
1102
|
const res = await fetchFn(`http://${probeHostname(live.hostname)}:${live.port}/v1/models`, {
|
|
1097
1103
|
signal: AbortSignal.timeout(EXPOSED_MODELS_TIMEOUT_MS),
|
|
1098
1104
|
});
|
|
1099
1105
|
if (!res.ok) return null;
|
|
1100
1106
|
const body = await res.json() as { data?: unknown };
|
|
1101
1107
|
if (!Array.isArray(body?.data)) return null;
|
|
1108
|
+
if (body.data.length > EXPOSED_MODELS_MAX_ROWS) return null;
|
|
1102
1109
|
const ids = new Set<string>();
|
|
1103
1110
|
for (const row of body.data) {
|
|
1104
1111
|
const id = (row as { id?: unknown } | null)?.id;
|
|
1105
|
-
if (typeof id
|
|
1112
|
+
if (typeof id !== "string") return null;
|
|
1113
|
+
if (id.length === 0) continue;
|
|
1114
|
+
if (id.length > EXPOSED_MODEL_ID_MAX_LENGTH) return null;
|
|
1115
|
+
ids.add(id);
|
|
1106
1116
|
}
|
|
1107
1117
|
return ids;
|
|
1108
1118
|
} catch {
|
|
@@ -1158,7 +1168,7 @@ export async function collectDefaultModelExposure(
|
|
|
1158
1168
|
}
|
|
1159
1169
|
|
|
1160
1170
|
const live = deps.live ?? null;
|
|
1161
|
-
const proxyIds = live ? await fetchExposedModelIds(live, deps.fetchFn ??
|
|
1171
|
+
const proxyIds = live ? await fetchExposedModelIds(live, deps.fetchFn ?? directLocalHttpFetch) : null;
|
|
1162
1172
|
const catalogIds = catalogExposedModelIds((deps.readCatalogModelsFn ?? defaultCatalogModels)());
|
|
1163
1173
|
if (proxyIds === null && catalogIds === null) {
|
|
1164
1174
|
return {
|
|
@@ -277,11 +277,38 @@ export function applyReasoningLevels(
|
|
|
277
277
|
* accident disappeared, and the sync path's else-branch
|
|
278
278
|
* (`applyReasoningLevels(entry, ["low","medium","high","xhigh"])`) would have truncated the
|
|
279
279
|
* shipped ladder, silently dropping `max` and `ultra`.
|
|
280
|
+
*
|
|
281
|
+
* Membership means "new-ladder native: keep the pinned ladder, never synthesize old-ladder top
|
|
282
|
+
* rungs" — NOT "advertise ultra". Returning false for a self-described row would be worse, not
|
|
283
|
+
* safer: every false branch (`finishUpstreamNativeEntry`, the persisted-row path in
|
|
284
|
+
* build-entries) calls `ensureUltraReasoningLevel`, which would hand `ultra` to `gpt-6-luna`.
|
|
285
|
+
* Whether `ultra` is added is decided separately by `nativeLadderIncludesUltra`.
|
|
286
|
+
*
|
|
287
|
+
* A capability alias of a self-described row qualifies through its source (`gpt-6-astra-minor`
|
|
288
|
+
* borrows `gpt-6-astra`), the same way Daybreak Blue qualifies through `gpt-5.6-sol`.
|
|
280
289
|
*/
|
|
281
290
|
export function isGpt56NativeSlug(slug: string): boolean {
|
|
282
291
|
if (slug.includes("/")) return false;
|
|
283
|
-
|
|
284
|
-
|
|
292
|
+
const sourceSlug = nativeOpenAiCapabilitySourceSlug(slug);
|
|
293
|
+
if (SELF_DESCRIBED_NATIVE_OPENAI_MODELS.has(sourceSlug)) return true;
|
|
294
|
+
return sourceSlug.startsWith("gpt-5.6-");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Whether a new-ladder native may advertise `ultra`.
|
|
299
|
+
*
|
|
300
|
+
* GPT-5.6 keeps its historical behaviour (always advertised; the wire clamp maps it down). A
|
|
301
|
+
* self-described GPT-6 row answers from its OWN pinned ladder, or its source's for an alias:
|
|
302
|
+
* the 2026-09-23 roster probe (`/backend-api/codex/models?client_version=0.155.0`) ships
|
|
303
|
+
* `gpt-6-sol` with low..ultra but `gpt-6-luna` with low..max, and advertising a rung upstream
|
|
304
|
+
* never listed would let a subagent spawn request an effort the model does not have.
|
|
305
|
+
*/
|
|
306
|
+
export function nativeLadderIncludesUltra(slug: string): boolean {
|
|
307
|
+
const sourceSlug = nativeOpenAiCapabilitySourceSlug(slug);
|
|
308
|
+
if (!SELF_DESCRIBED_NATIVE_OPENAI_MODELS.has(sourceSlug)) return true;
|
|
309
|
+
const levels = UPSTREAM_NATIVE_ENTRIES.get(sourceSlug)?.supported_reasoning_levels;
|
|
310
|
+
return Array.isArray(levels)
|
|
311
|
+
&& (levels as Array<{ effort?: string }>).some(level => level?.effort === "ultra");
|
|
285
312
|
}
|
|
286
313
|
|
|
287
314
|
export function ensureGpt56ReasoningLevels(entry: RawEntry): void {
|
|
@@ -289,8 +316,12 @@ export function ensureGpt56ReasoningLevels(entry: RawEntry): void {
|
|
|
289
316
|
? entry.supported_reasoning_levels as Array<Partial<CodexReasoningLevel>>
|
|
290
317
|
: [];
|
|
291
318
|
const out = [...levels];
|
|
292
|
-
// max is a real native rung on the 5.6 family — always restored
|
|
293
|
-
|
|
319
|
+
// max is a real native rung on the 5.6 family — always restored. ultra is advertised unless
|
|
320
|
+
// the slug's pinned ladder (or its source's) stops short of it, as gpt-6-luna's does.
|
|
321
|
+
const wanted = typeof entry.slug === "string" && !nativeLadderIncludesUltra(entry.slug)
|
|
322
|
+
? ["max"]
|
|
323
|
+
: ["max", "ultra"];
|
|
324
|
+
for (const effort of wanted) {
|
|
294
325
|
if (out.some(level => level.effort === effort)) continue;
|
|
295
326
|
out.push(CODEX_REASONING_LEVELS.find(level => level.effort === effort)
|
|
296
327
|
?? { effort, description: `${effort} reasoning` });
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
import type { NormalizedComboConfig } from "../../combos/types";
|
|
32
32
|
import { providerDestinationResolvedError } from "../../lib/destination-policy";
|
|
33
33
|
import { redactSecretString } from "../../lib/redact";
|
|
34
|
-
import
|
|
34
|
+
import { pinnedNativeModelRows } from "./pinned-models";
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
import type { RawEntry } from "./parsing";
|
|
@@ -42,7 +42,10 @@ import { RESERVE_METADATA_SOURCE_FIELD } from "./reserve";
|
|
|
42
42
|
import {
|
|
43
43
|
ACCOUNT_GATED_NATIVE_OPENAI_MODELS,
|
|
44
44
|
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
45
|
+
NATIVE_GPT6_ASTRA_MINOR_MODEL,
|
|
45
46
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
47
|
+
NATIVE_GPT6_LUNA_MODEL,
|
|
48
|
+
NATIVE_GPT6_SOL_MODEL,
|
|
46
49
|
NATIVE_RESERVE_MODEL,
|
|
47
50
|
NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS,
|
|
48
51
|
NATIVE_OPENAI_MODELS,
|
|
@@ -59,7 +62,10 @@ import { MAIN_CODEX_ACCOUNT_ID } from "../main-account";
|
|
|
59
62
|
export { CODEX_NATIVE_ALIAS_CATALOG_KIND } from "./kinds";
|
|
60
63
|
export {
|
|
61
64
|
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
65
|
+
NATIVE_GPT6_ASTRA_MINOR_MODEL,
|
|
62
66
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
67
|
+
NATIVE_GPT6_LUNA_MODEL,
|
|
68
|
+
NATIVE_GPT6_SOL_MODEL,
|
|
63
69
|
NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS,
|
|
64
70
|
NATIVE_OPENAI_MODELS,
|
|
65
71
|
SELF_DESCRIBED_NATIVE_OPENAI_MODELS,
|
|
@@ -75,6 +81,10 @@ export const DOCUMENTED_NATIVE_OPENAI_ADDITIONS = [
|
|
|
75
81
|
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
76
82
|
// The shipped pin also backfills older installed Codex catalogs that predate Astra.
|
|
77
83
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
84
|
+
// Same backfill for Sol and Luna from the roster pin: the live roster serves them only to
|
|
85
|
+
// client_version >= 0.155.0, so an installed catalog built by an older client lacks them.
|
|
86
|
+
// Astra Minor is deliberately absent: it is gated, and nativeOpenAiSlugs() would drop it anyway.
|
|
87
|
+
NATIVE_GPT6_SOL_MODEL, NATIVE_GPT6_LUNA_MODEL,
|
|
78
88
|
];
|
|
79
89
|
|
|
80
90
|
export function configuredNativeAliasSlugs(
|
|
@@ -177,11 +187,24 @@ export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record<string, { contextWindow?: n
|
|
|
177
187
|
// maxInputTokens is clamped to the resolved window by nativeOpenAiMaxInputTokens, so this reads
|
|
178
188
|
// 272,000 by default and 872,000 only under the long-window opt-in.
|
|
179
189
|
[NATIVE_GPT6_ASTRA_MODEL]: { contextWindow: 272_000, maxContextWindow: 872_000, maxInputTokens: 872_000 },
|
|
190
|
+
// Sol and Luna ship the same 272,000 / 872,000 pair in their roster rows (probe of
|
|
191
|
+
// /backend-api/codex/models?client_version=0.155.0, 2026-09-23). Unmeasured here, so they take
|
|
192
|
+
// the row's own ceiling rather than the GPT-5.6 family's measured 922,000.
|
|
193
|
+
[NATIVE_GPT6_SOL_MODEL]: { contextWindow: 272_000, maxContextWindow: 872_000, maxInputTokens: 872_000 },
|
|
194
|
+
[NATIVE_GPT6_LUNA_MODEL]: { contextWindow: 272_000, maxContextWindow: 872_000, maxInputTokens: 872_000 },
|
|
195
|
+
// Astra Minor borrows Astra's row, so it inherits Astra's numbers. No account we hold can reach
|
|
196
|
+
// it, so this is inheritance, not a measurement.
|
|
197
|
+
[NATIVE_GPT6_ASTRA_MINOR_MODEL]: { contextWindow: 272_000, maxContextWindow: 872_000, maxInputTokens: 872_000 },
|
|
180
198
|
};
|
|
181
199
|
|
|
200
|
+
// Snapshot rows plus roster-captured rows the snapshot lacks (see pinned-models.ts).
|
|
182
201
|
const PINNED_UPSTREAM_MODELS: Map<string, RawEntry> = new Map(
|
|
183
|
-
((
|
|
184
|
-
|
|
202
|
+
(pinnedNativeModelRows() as unknown as ReadonlyArray<RawEntry>)
|
|
203
|
+
// Upstream stopped shipping top-level `base_instructions` (openai/codex #43604); every row
|
|
204
|
+
// still carries `model_messages.instructions_template`. Derive at projection time so the
|
|
205
|
+
// pinned JSON stays byte-identical to upstream while `hasNativeCatalogRowShape` and the alias
|
|
206
|
+
// rewrite in `upstreamNativeEntryForSlug` keep seeing the field they test for.
|
|
207
|
+
.flatMap(model => typeof model.slug === "string" ? [[model.slug, withDerivedBaseInstructions(model)] as const] : []),
|
|
185
208
|
);
|
|
186
209
|
|
|
187
210
|
function pinnedNativeCapabilityEntry(slug: string): RawEntry | undefined {
|
|
@@ -536,14 +559,19 @@ function upstreamNativeEntryForSlug(slug: string): RawEntry | undefined {
|
|
|
536
559
|
// reserved for slugs that genuinely borrow another model's identity. The allowlist is explicit
|
|
537
560
|
// rather than "has a pinned entry", which would also admit gpt-5.5/gpt-5.2/codex-auto-review into
|
|
538
561
|
// the sync-replacement authority this map carries.
|
|
539
|
-
|
|
562
|
+
// Keyed on the SOURCE so an alias of a self-described row (gpt-6-astra-minor -> gpt-6-astra)
|
|
563
|
+
// is admitted the same way an alias of a GPT-5.6 row (Daybreak -> Sol) always was; for a
|
|
564
|
+
// self-described slug itself the source is the slug, so nothing else changes.
|
|
565
|
+
if (!sourceSlug.startsWith("gpt-5.6-") && !SELF_DESCRIBED_NATIVE_OPENAI_MODELS.has(sourceSlug)) {
|
|
540
566
|
return undefined;
|
|
541
567
|
}
|
|
542
568
|
const source = PINNED_UPSTREAM_MODELS.get(sourceSlug);
|
|
543
569
|
if (!source) return undefined;
|
|
544
570
|
if (slug === sourceSlug) return withDerivedBaseInstructions(source);
|
|
545
571
|
|
|
546
|
-
|
|
572
|
+
// Derive before cloning: Astra ships only model_messages, and an alias row without
|
|
573
|
+
// base_instructions fails the native row-shape checks just as its source would.
|
|
574
|
+
const alias = structuredClone(withDerivedBaseInstructions(source)) as RawEntry;
|
|
547
575
|
alias.slug = slug;
|
|
548
576
|
const presentation = nativeOpenAiAliasPresentation(slug);
|
|
549
577
|
if (!presentation) return undefined; // an alias with no product identity must not ship a wrong one
|
|
@@ -22,6 +22,32 @@ export const NATIVE_DAYBREAK_BLUE_MODEL = "gpt-daybreak-blue-latest";
|
|
|
22
22
|
*/
|
|
23
23
|
export const NATIVE_GPT6_ASTRA_MODEL = "gpt-6-astra";
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* GPT-6 Sol and Luna, announced 2026-09-22 (https://openai.com/index/introducing-gpt-6-sol-and-luna/).
|
|
27
|
+
*
|
|
28
|
+
* SELF-DESCRIBED: the authenticated roster probe on 2026-09-23
|
|
29
|
+
* (`/backend-api/codex/models?client_version=0.155.0`, main account) returned a full row for each,
|
|
30
|
+
* pinned verbatim in `src/codex/data/roster-pinned-models.json` because codex-rs has not bundled
|
|
31
|
+
* them yet. Sol ships low..ultra; Luna ships low..max and must not be widened to ultra.
|
|
32
|
+
*
|
|
33
|
+
* Not account-gated, for the same owner decision that ungated `gpt-6-astra`: the rows list 24
|
|
34
|
+
* plans, and hiding a flagship until a roster confirms it reads as opencodex losing the model.
|
|
35
|
+
* Listing them means the request dispatches and the user sees the real upstream status.
|
|
36
|
+
*/
|
|
37
|
+
export const NATIVE_GPT6_SOL_MODEL = "gpt-6-sol";
|
|
38
|
+
export const NATIVE_GPT6_LUNA_MODEL = "gpt-6-luna";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Unreleased GPT-6 Astra variant. No public row exists anywhere — neither the codex-rs bundle nor
|
|
42
|
+
* the 2026-09-23 main-account roster probe carries it — so it is ACCOUNT-GATED: hidden and
|
|
43
|
+
* request-refused until an authenticated `/models` roster lists it for that account. Absence is
|
|
44
|
+
* the only signal that exists for it, which is exactly the Daybreak Blue situation.
|
|
45
|
+
*
|
|
46
|
+
* Capability metadata is borrowed from `gpt-6-astra` (a capability alias); presentation is its
|
|
47
|
+
* own. No minimum client version is recorded for it: none has been measured.
|
|
48
|
+
*/
|
|
49
|
+
export const NATIVE_GPT6_ASTRA_MINOR_MODEL = "gpt-6-astra-minor";
|
|
50
|
+
|
|
25
51
|
/**
|
|
26
52
|
* Native ChatGPT/Codex ids whose availability is proven per authenticated account.
|
|
27
53
|
*
|
|
@@ -49,6 +75,8 @@ export const NATIVE_GPT6_ASTRA_MODEL = "gpt-6-astra";
|
|
|
49
75
|
*/
|
|
50
76
|
export const ACCOUNT_GATED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
|
|
51
77
|
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
78
|
+
// Same footing as Daybreak: no shipped row, so absence is the only evidence available.
|
|
79
|
+
NATIVE_GPT6_ASTRA_MINOR_MODEL,
|
|
52
80
|
]);
|
|
53
81
|
|
|
54
82
|
/**
|
|
@@ -65,6 +93,7 @@ export const ACCOUNT_GATED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
|
|
|
65
93
|
*/
|
|
66
94
|
const NATIVE_OPENAI_CAPABILITY_SOURCES: Readonly<Record<string, string>> = Object.freeze({
|
|
67
95
|
[NATIVE_DAYBREAK_BLUE_MODEL]: "gpt-5.6-sol",
|
|
96
|
+
[NATIVE_GPT6_ASTRA_MINOR_MODEL]: NATIVE_GPT6_ASTRA_MODEL,
|
|
68
97
|
});
|
|
69
98
|
|
|
70
99
|
/**
|
|
@@ -72,14 +101,17 @@ const NATIVE_OPENAI_CAPABILITY_SOURCES: Readonly<Record<string, string>> = Objec
|
|
|
72
101
|
*
|
|
73
102
|
* Membership authorizes `upstreamNativeEntryForSlug` to return the pinned entry directly. It is
|
|
74
103
|
* an explicit list, not a structural `PINNED_UPSTREAM_MODELS.has(slug)` predicate: the pin also
|
|
75
|
-
* holds `gpt-5.5`, `
|
|
104
|
+
* holds `gpt-5.5`, `codex-auto-review` and the Daybreak rows, and admitting those into
|
|
76
105
|
* `UPSTREAM_NATIVE_ENTRIES` would newly authorize replacing their persisted catalog rows during
|
|
77
106
|
* sync — an invariant that map's own comment reserves for the GPT-5.6 family. The snapshot
|
|
78
107
|
* keeps rows this runtime does not expose, which is exactly why presence in the pin cannot be
|
|
79
|
-
* the predicate: `gpt-5.4`
|
|
108
|
+
* the predicate: `gpt-5.4` is still pinned (hidden, with an upgrade to Terra) after its retirement.
|
|
80
109
|
*/
|
|
81
110
|
export const SELF_DESCRIBED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
|
|
82
111
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
112
|
+
// Rows come from roster-pinned-models.json via pinnedNativeModelRows(), not the codex-rs pin.
|
|
113
|
+
NATIVE_GPT6_SOL_MODEL,
|
|
114
|
+
NATIVE_GPT6_LUNA_MODEL,
|
|
83
115
|
]);
|
|
84
116
|
|
|
85
117
|
/**
|
|
@@ -132,6 +164,10 @@ export const NATIVE_OPENAI_ALIAS_PRESENTATION: Readonly<Record<string, { display
|
|
|
132
164
|
displayName: "Daybreak Blue",
|
|
133
165
|
description: "Frontier general-purpose model with safeguards for defensive cybersecurity work.",
|
|
134
166
|
},
|
|
167
|
+
[NATIVE_GPT6_ASTRA_MINOR_MODEL]: {
|
|
168
|
+
displayName: "GPT-6-Astra-Minor",
|
|
169
|
+
description: "Unreleased GPT-6 Astra variant; shown only when your account's Codex roster lists it.",
|
|
170
|
+
},
|
|
135
171
|
});
|
|
136
172
|
|
|
137
173
|
export function nativeOpenAiAliasPresentation(slug: string): { displayName: string; description: string } | undefined {
|
|
@@ -159,6 +195,8 @@ export const NATIVE_OPENAI_MODELS = [
|
|
|
159
195
|
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
160
196
|
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
161
197
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
198
|
+
NATIVE_GPT6_SOL_MODEL, NATIVE_GPT6_LUNA_MODEL,
|
|
199
|
+
NATIVE_GPT6_ASTRA_MINOR_MODEL,
|
|
162
200
|
];
|
|
163
201
|
|
|
164
202
|
export const SUPPORTED_NATIVE_OPENAI_SLUGS = new Set(NATIVE_OPENAI_MODELS);
|
|
@@ -204,4 +242,7 @@ export const NATIVE_MAIN_DRAIN_SENTINEL_MODELS: ReadonlySet<string> = new Set([
|
|
|
204
242
|
"gpt-5.6-terra",
|
|
205
243
|
"gpt-5.6-luna",
|
|
206
244
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
245
|
+
// Astra Minor arrives through the gated spread above; Sol and Luna are ungated flagships.
|
|
246
|
+
NATIVE_GPT6_SOL_MODEL,
|
|
247
|
+
NATIVE_GPT6_LUNA_MODEL,
|
|
207
248
|
]);
|