@bitkyc08/opencodex 2.60.0 → 2.61.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 +64 -0
- package/README.md +28 -1
- package/bin/ocx.mjs +382 -209
- package/gui/dist/assets/App-CH6C5H7x.js +50 -0
- package/gui/dist/assets/Tray-CLZh48fM.js +1 -0
- package/gui/dist/assets/index-_bpvxJu0.css +1 -0
- package/gui/dist/assets/index-wpTOyepx.js +86 -0
- package/gui/dist/assets/usage-companion-chart-a0N58rRI.js +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/stepfun-color.svg +1 -0
- package/package.json +5 -1
- package/src/adapters/anthropic.ts +16 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +10 -2
- package/src/adapters/command-code.ts +2 -1
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +13 -3
- package/src/adapters/cursor.ts +11 -2
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin.ts +75 -23
- package/src/adapters/google-antigravity-wire.ts +5 -2
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google.ts +29 -5
- package/src/adapters/image.ts +4 -1
- package/src/adapters/input-media-guard.ts +21 -9
- package/src/adapters/kiro/usage.ts +3 -2
- package/src/adapters/kiro-tool-fallback.ts +1 -1
- package/src/adapters/ollama-native.ts +6 -0
- package/src/adapters/openai-chat/developer-role.ts +61 -0
- package/src/adapters/openai-chat/messages.ts +46 -27
- package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
- package/src/adapters/openai-chat/passthrough.ts +33 -9
- package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
- package/src/adapters/openai-chat.ts +18 -57
- package/src/adapters/openai-responses/passthrough.ts +2 -0
- package/src/adapters/registry.ts +3 -2
- package/src/adapters/run-turn-queue.ts +178 -29
- package/src/adapters/xai-web-search.ts +16 -1
- package/src/bridge/errors.ts +8 -2
- package/src/bridge/response-json.ts +9 -1
- package/src/bridge/sse.ts +10 -0
- package/src/chat/inbound.ts +141 -5
- package/src/claude/desktop-3p.ts +7 -1
- package/src/claude/desktop-first-party.ts +183 -0
- package/src/claude/desktop-gateway-state.ts +41 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/claude/inbound.ts +32 -6
- package/src/claude/intercept/connect-proxy.ts +179 -0
- package/src/claude/intercept/listener.ts +122 -0
- package/src/claude/intercept/local-ca.ts +298 -0
- package/src/claude/intercept/runtime.ts +98 -0
- package/src/claude/intercept/settings.ts +189 -0
- package/src/cli/access.ts +87 -0
- package/src/cli/account-auth.ts +19 -0
- package/src/cli/capabilities.ts +31 -0
- package/src/cli/claude-desktop.ts +206 -16
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/companion.ts +56 -0
- package/src/cli/dispatch.ts +43 -4
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/index.ts +200 -61
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -1
- package/src/cli/registry.ts +41 -2
- package/src/cli/resolve.ts +230 -0
- package/src/cli/root.ts +24 -1
- package/src/cli/start-ownership-publication.ts +56 -0
- package/src/cli/status-probes.ts +2 -18
- package/src/cli/status.ts +62 -0
- package/src/cli/stop-report.ts +143 -0
- package/src/cli/uninstall-plan.ts +9 -0
- package/src/client/machine-listener.ts +2 -5
- package/src/clients/aside-profiles.ts +4 -0
- package/src/clients/config-export/zcode-store.ts +157 -0
- package/src/clients/config-export.ts +36 -0
- package/src/codex/app-server-processes.ts +72 -40
- package/src/codex/auth-api/login-flow.ts +6 -1
- package/src/codex/autostart-health.ts +28 -0
- package/src/codex/catalog/build-entries.ts +2 -2
- package/src/codex/catalog/effort.ts +3 -3
- package/src/codex/catalog/provider-models.ts +24 -15
- package/src/codex/catalog/retained-sync.ts +2 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/history-provider.ts +12 -1
- package/src/codex/inject/config-toml.ts +41 -6
- package/src/codex/inject/paginated-openai-compat.ts +90 -0
- package/src/codex/inject.ts +18 -15
- package/src/codex/injected-marker.ts +18 -0
- package/src/codex/main-account.ts +6 -0
- package/src/codex/model-cache.ts +52 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +87 -44
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +5 -41
- package/src/codex/shim-templates.ts +29 -3
- package/src/companion/settings.ts +132 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/load-degrade.ts +34 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/schema/config-schema.ts +27 -1
- package/src/config/schema/leaf-validators.ts +47 -0
- package/src/config.ts +1 -1
- package/src/generated/compatibility-version.json +418 -174
- package/src/integrations/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +124 -18
- package/src/integrations/registry.ts +38 -0
- package/src/integrations/state.ts +78 -45
- package/src/integrations/target.ts +208 -0
- package/src/integrations/writer.ts +49 -11
- package/src/lab/conformance/fixture-provider.ts +5 -0
- package/src/lib/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug.ts +40 -0
- package/src/lib/open-url.ts +51 -7
- package/src/lib/package-tree-integrity.ts +2 -1
- package/src/lib/package-version.ts +8 -0
- package/src/lib/provider-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +59 -14
- package/src/lib/proxy-env.ts +82 -7
- package/src/lib/request-execution-budget.ts +72 -0
- package/src/lib/request-failure-attribution.ts +183 -0
- package/src/lib/request-failure-model.ts +236 -0
- package/src/lib/request-resend-gate.ts +138 -0
- package/src/lib/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/index.ts +24 -1
- package/src/oauth/login-cli.ts +80 -29
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/key-failover.ts +31 -1
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +124 -37
- package/src/providers/quota/vendor-probes-key.ts +37 -22
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +9 -4
- package/src/providers/registry/entries-extended.ts +29 -4
- package/src/providers/registry/model-seeds.ts +47 -10
- package/src/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -0
- package/src/responses/function-call-compat.ts +38 -1
- package/src/responses/inline-document.ts +65 -0
- package/src/responses/input-media.ts +42 -8
- package/src/responses/muse-tool-name-alias.ts +19 -0
- package/src/responses/parser-content.ts +8 -2
- package/src/responses/parser-tools.ts +3 -0
- package/src/responses/parser.ts +3 -1
- package/src/responses/schema.ts +3 -0
- package/src/router.ts +17 -2
- package/src/server/admission-model-scope.ts +219 -0
- package/src/server/audio-live.ts +9 -3
- package/src/server/audio-upstream.ts +18 -0
- package/src/server/auth-cors.ts +26 -0
- package/src/server/chat-completions.ts +55 -2
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +55 -17
- package/src/server/grok-responses-snapshot-repair.ts +113 -11
- package/src/server/gui-freshness.ts +103 -0
- package/src/server/gui-static.ts +7 -9
- package/src/server/images.ts +59 -6
- package/src/server/index/claude-intercept-lifecycle.ts +49 -0
- package/src/server/index/serve-options.ts +56 -10
- package/src/server/index/spend-ledger-lifecycle.ts +34 -8
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index.ts +21 -28
- package/src/server/lifecycle.ts +4 -4
- package/src/server/live-call-bindings.ts +6 -0
- package/src/server/live.ts +88 -3
- package/src/server/management/agent-settings-routes.ts +121 -36
- package/src/server/management/companion-routes.ts +77 -0
- package/src/server/management/logs-usage-routes.ts +19 -0
- package/src/server/management/native-integration-routes.ts +103 -6
- package/src/server/management/oauth-account-routes.ts +45 -7
- package/src/server/management/route-registry.ts +6 -0
- package/src/server/management/shared.ts +18 -1
- package/src/server/management/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +8 -9
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/relay.ts +19 -2
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +114 -0
- package/src/server/request-metrics.ts +92 -30
- package/src/server/responses/codex-ws-wire.ts +34 -8
- package/src/server/responses/combo-stream-preflight.ts +168 -6
- package/src/server/responses/compact.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +90 -0
- package/src/server/responses/fetch-helpers.ts +124 -8
- package/src/server/responses/input-admission.ts +10 -0
- package/src/server/responses/passthrough-delivery.ts +14 -1
- package/src/server/responses/passthrough-dispatch.ts +179 -35
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/request-prepare.ts +42 -1
- package/src/server/responses/request-send-budget.ts +12 -0
- package/src/server/responses/request-transport.ts +24 -4
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +4 -1
- package/src/server/search.ts +25 -1
- package/src/server/usage-ledger-retention.ts +73 -0
- package/src/service/cli.ts +48 -2
- package/src/service/health.ts +3 -2
- package/src/service/install-state-contract.d.mts +27 -0
- package/src/service/install-state-contract.mjs +34 -0
- package/src/service/launchd.ts +1 -1
- package/src/service/orchestration.ts +2 -4
- package/src/service/ownership-compatibility.ts +164 -0
- package/src/service/ownership-mutation-lease.d.mts +32 -0
- package/src/service/ownership-mutation-lease.mjs +211 -0
- package/src/service/repair.ts +45 -1
- package/src/service/state-lock.ts +269 -0
- package/src/service/state-record.d.mts +36 -0
- package/src/service/state-record.mjs +138 -0
- package/src/service/state.ts +582 -68
- package/src/service/windows-taskxml.ts +11 -10
- package/src/service.ts +7 -3
- package/src/tray/windows-tray.ps1 +1 -1
- package/src/types/config.ts +37 -0
- package/src/types/provider.ts +73 -0
- package/src/types/request.ts +28 -2
- package/src/types/tools.ts +19 -0
- package/src/types.ts +3 -0
- package/src/update/index.ts +207 -63
- package/src/update/job.ts +9 -5
- package/src/update/ownership-transaction.ts +47 -0
- package/src/update/restart-ownership.ts +54 -0
- package/src/update/runtime-ownership.d.mts +40 -0
- package/src/update/runtime-ownership.mjs +122 -0
- package/src/usage/attempt-delivery.ts +198 -0
- package/src/usage/cache-diagnostic.ts +305 -0
- package/src/usage/failure-fingerprint.ts +118 -0
- package/src/usage/failure-projection-cache.ts +174 -0
- package/src/usage/failure-projection.ts +174 -0
- package/src/usage/ledger-retention.ts +165 -0
- package/src/usage/log.ts +126 -79
- package/src/usage/request-outcome.ts +150 -0
- package/src/usage/retention-contract.ts +28 -0
- package/src/usage/summary.ts +2 -2
- package/src/usage/telemetry-contract.ts +237 -0
- package/src/usage/timeline.ts +236 -0
- package/src/web-search/alpha-search.ts +21 -1
- package/gui/dist/assets/index-BTuCbqQd.css +0 -1
- package/gui/dist/assets/index-DoBVdPHP.js +0 -134
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// ZCode personal provider store export.
|
|
2
|
+
import type { ExportContext, ManagedContribution, ManagedFragment } from "./contracts";
|
|
3
|
+
import { authoritativeContextWindow, inputModalitiesForClient, normalizeExportModels } from "./model-metadata";
|
|
4
|
+
import { OPENCODE_PROVIDER_ID, LOOPBACK_API_KEY_PLACEHOLDER } from "./constants";
|
|
5
|
+
import { formatSelectorConjunction } from "../../integrations/merge";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The file ZCode 3.14 and later actually read their custom providers from.
|
|
9
|
+
*
|
|
10
|
+
* `v2/config.json` — the file {@link buildZcodeContribution} writes — is still
|
|
11
|
+
* parsed by this client, but only by a one-shot `importLegacy` hook that runs
|
|
12
|
+
* when this store is missing. The client creates the store on first launch, so
|
|
13
|
+
* on any install that has ever run the import is already spent and a later
|
|
14
|
+
* write to the old file is read by nobody (#5348).
|
|
15
|
+
*
|
|
16
|
+
* The shape below is the client's own, quoted from the report that opened
|
|
17
|
+
* #5348: it is what the client's `importLegacyPersonalProviderConfig` produced
|
|
18
|
+
* when it migrated this project's block, so it is observed output rather than a
|
|
19
|
+
* schema this project invented. Everything the client persists that we have not
|
|
20
|
+
* seen it derive from our block is deliberately absent — an omitted field is a
|
|
21
|
+
* field the client still owns.
|
|
22
|
+
*/
|
|
23
|
+
export const ZCODE_STORE_SCHEMA_VERSION = 1;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `group` places the provider among the user's own personal providers rather
|
|
27
|
+
* than the builtin list the client ships and rewrites. `api.type` is the same
|
|
28
|
+
* choice `kind: "openai"` makes in the legacy block: the OpenAI Responses
|
|
29
|
+
* protocol, which is the only surface this proxy speaks natively.
|
|
30
|
+
*/
|
|
31
|
+
export const ZCODE_STORE_PROVIDER_GROUP = "standard-personal";
|
|
32
|
+
export const ZCODE_STORE_API_TYPE = "openai-responses";
|
|
33
|
+
export const ZCODE_STORE_PROVIDER_NAME = "OpenCodex";
|
|
34
|
+
|
|
35
|
+
/** Where the store keeps one rule per provider, and one per (provider, model). */
|
|
36
|
+
export const ZCODE_STORE_PROVIDER_RULES_PATH = ["config", "providerConfigRules", "providerRules"] as const;
|
|
37
|
+
export const ZCODE_STORE_MODEL_RULES_PATH = ["config", "modelConfigRules", "providerModelRules"] as const;
|
|
38
|
+
|
|
39
|
+
export interface ZcodeStoreProviderRule {
|
|
40
|
+
providerId: string;
|
|
41
|
+
enabled: true;
|
|
42
|
+
providerName: string;
|
|
43
|
+
config: {
|
|
44
|
+
group: string;
|
|
45
|
+
access: { apiKey: string };
|
|
46
|
+
api: { type: string; baseUrl: string };
|
|
47
|
+
personalModelIds: string[];
|
|
48
|
+
modelOrder: string[];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ZcodeStoreModelRule {
|
|
53
|
+
providerId: string;
|
|
54
|
+
modelId: string;
|
|
55
|
+
config: { properties: { contextWindow: number } };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Is this document a store whose schema we can write?
|
|
60
|
+
*
|
|
61
|
+
* Only the one version whose shape has been observed. A store carrying any
|
|
62
|
+
* other `schemaVersion` — or none, or a document that is not an object — is a
|
|
63
|
+
* file we cannot merge into without asserting a nesting we have never seen,
|
|
64
|
+
* and a wrong assertion there does not fail loudly: it replaces the provider
|
|
65
|
+
* list the user keeps in the same file. The integration reports the write as
|
|
66
|
+
* ineffective in that case instead of guessing.
|
|
67
|
+
*/
|
|
68
|
+
export function zcodeStoreSchemaEstablished(parsed: unknown): boolean {
|
|
69
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return false;
|
|
70
|
+
return (parsed as Record<string, unknown>).schemaVersion === ZCODE_STORE_SCHEMA_VERSION;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The selector addressing one model rule, or null when the id cannot be spelled.
|
|
75
|
+
*
|
|
76
|
+
* A rule is identified by the PAIR, so both fields are named: a selector
|
|
77
|
+
* carrying only the model would match another provider's rule for the same
|
|
78
|
+
* model. The conjunction grammar reserves `,` and `]`, and a model id holding
|
|
79
|
+
* either cannot be addressed unambiguously — that row ships without its
|
|
80
|
+
* context window rather than with a selector that points somewhere else.
|
|
81
|
+
*/
|
|
82
|
+
function modelRuleSelector(modelId: string): string | null {
|
|
83
|
+
return formatSelectorConjunction([
|
|
84
|
+
{ field: "providerId", value: OPENCODE_PROVIDER_ID },
|
|
85
|
+
{ field: "modelId", value: modelId },
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The rows this export publishes, filtered exactly as the legacy block filters them. */
|
|
90
|
+
function storeModels(ctx: ExportContext): string[] {
|
|
91
|
+
const ids: string[] = [];
|
|
92
|
+
for (const model of normalizeExportModels(ctx.models)) {
|
|
93
|
+
if (inputModalitiesForClient("pi", model.inputModalities) === null) continue;
|
|
94
|
+
ids.push(model.namespaced);
|
|
95
|
+
}
|
|
96
|
+
return ids;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The provider rule the client reads, built from the same context the legacy
|
|
101
|
+
* block is built from.
|
|
102
|
+
*
|
|
103
|
+
* `baseUrl` carries the `/v1` root for the same reason the legacy block does:
|
|
104
|
+
* this client appends `/responses` for the Responses protocol, so requests land
|
|
105
|
+
* on `/v1/responses`. The serialized credential is always the non-secret
|
|
106
|
+
* loopback placeholder.
|
|
107
|
+
*/
|
|
108
|
+
export function buildZcodeStoreProviderRule(ctx: ExportContext): ZcodeStoreProviderRule {
|
|
109
|
+
const ids = storeModels(ctx);
|
|
110
|
+
return {
|
|
111
|
+
providerId: OPENCODE_PROVIDER_ID,
|
|
112
|
+
enabled: true,
|
|
113
|
+
providerName: ZCODE_STORE_PROVIDER_NAME,
|
|
114
|
+
config: {
|
|
115
|
+
group: ZCODE_STORE_PROVIDER_GROUP,
|
|
116
|
+
access: { apiKey: LOOPBACK_API_KEY_PLACEHOLDER },
|
|
117
|
+
api: { type: ZCODE_STORE_API_TYPE, baseUrl: `${ctx.baseUrl.replace(/\/v1\/?$/, "")}/v1` },
|
|
118
|
+
personalModelIds: ids,
|
|
119
|
+
/*
|
|
120
|
+
* The client persists the picker order separately from membership. Ours is
|
|
121
|
+
* the catalog order the rest of this export already sorts by, so a refresh
|
|
122
|
+
* that adds a model puts it where every other surface puts it.
|
|
123
|
+
*/
|
|
124
|
+
modelOrder: [...ids],
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Everything this project owns inside the store: one provider rule, plus one
|
|
131
|
+
* model rule per row that has an authoritative context window.
|
|
132
|
+
*
|
|
133
|
+
* A row without one ships no model rule at all, which is the same
|
|
134
|
+
* authoritative-window rule the legacy block follows — an emitted stand-in
|
|
135
|
+
* would be a guessed capability. Reasoning ladders have no counterpart here
|
|
136
|
+
* that this project has observed the client produce, so none is written.
|
|
137
|
+
*/
|
|
138
|
+
export function buildZcodeStoreContribution(ctx: ExportContext): ManagedContribution {
|
|
139
|
+
const fragments: ManagedFragment[] = [{
|
|
140
|
+
path: [...ZCODE_STORE_PROVIDER_RULES_PATH, `[providerId=${OPENCODE_PROVIDER_ID}]`],
|
|
141
|
+
value: buildZcodeStoreProviderRule(ctx),
|
|
142
|
+
}];
|
|
143
|
+
for (const model of normalizeExportModels(ctx.models)) {
|
|
144
|
+
if (inputModalitiesForClient("pi", model.inputModalities) === null) continue;
|
|
145
|
+
const contextWindow = authoritativeContextWindow(model.contextWindow);
|
|
146
|
+
if (contextWindow === undefined) continue;
|
|
147
|
+
const selector = modelRuleSelector(model.namespaced);
|
|
148
|
+
if (selector === null) continue;
|
|
149
|
+
const rule: ZcodeStoreModelRule = {
|
|
150
|
+
providerId: OPENCODE_PROVIDER_ID,
|
|
151
|
+
modelId: model.namespaced,
|
|
152
|
+
config: { properties: { contextWindow } },
|
|
153
|
+
};
|
|
154
|
+
fragments.push({ path: [...ZCODE_STORE_MODEL_RULES_PATH, selector], value: rule });
|
|
155
|
+
}
|
|
156
|
+
return { clientId: "zcode", fragments };
|
|
157
|
+
}
|
|
@@ -35,6 +35,18 @@ export { OPENCODE_PROVIDER_ID, OPENCODE_CONFIG_SCHEMA, OPENCODE_API_KEY_ENV, OPE
|
|
|
35
35
|
export { normalizeExportModels } from "./config-export/model-metadata";
|
|
36
36
|
export type { OmpModelEntry, OmpProviderBlock, OmpGeneratedConfig } from "./config-export/omp";
|
|
37
37
|
export type { ZcodeModelEntry, ZcodeProviderBlock, ZcodeGeneratedConfig } from "./config-export/zcode";
|
|
38
|
+
export type { ZcodeStoreProviderRule, ZcodeStoreModelRule } from "./config-export/zcode-store";
|
|
39
|
+
export {
|
|
40
|
+
ZCODE_STORE_SCHEMA_VERSION,
|
|
41
|
+
ZCODE_STORE_PROVIDER_GROUP,
|
|
42
|
+
ZCODE_STORE_API_TYPE,
|
|
43
|
+
ZCODE_STORE_PROVIDER_NAME,
|
|
44
|
+
ZCODE_STORE_PROVIDER_RULES_PATH,
|
|
45
|
+
ZCODE_STORE_MODEL_RULES_PATH,
|
|
46
|
+
buildZcodeStoreProviderRule,
|
|
47
|
+
buildZcodeStoreContribution,
|
|
48
|
+
zcodeStoreSchemaEstablished,
|
|
49
|
+
} from "./config-export/zcode-store";
|
|
38
50
|
export type { DshReasoningEffort, DshWireReasoningEffort, DshModelEntry, DshProviderBlock, DshGeneratedConfig } from "./config-export/dsh";
|
|
39
51
|
export type { McodeProviderBlock, McodeModelEntry, McodeGeneratedConfig } from "./config-export/mcode";
|
|
40
52
|
export type { RaycastAbility, RaycastAbilityName, RaycastModelEntry, RaycastProviderEntry, RaycastGeneratedConfig } from "./config-export/raycast";
|
|
@@ -447,6 +459,30 @@ export function zcodeConfigPath(env: OpencodeLaunchEnv = process.env, home: stri
|
|
|
447
459
|
return join(zcodeHomeDir(env, home), "v2", "config.json");
|
|
448
460
|
}
|
|
449
461
|
|
|
462
|
+
/**
|
|
463
|
+
* The provider store a current ZCode reads, which is NOT the file above.
|
|
464
|
+
*
|
|
465
|
+
* ZCode 3.14 moved custom providers to `v2/provider_config.json` and left
|
|
466
|
+
* `v2/config.json` reachable only through a one-shot import that runs when the
|
|
467
|
+
* new file is missing. The client creates the new file on first launch, so on
|
|
468
|
+
* an install that has ever run, the import has already happened and never runs
|
|
469
|
+
* again — every later write to `v2/config.json` is read by nobody (#5348).
|
|
470
|
+
*
|
|
471
|
+
* This project does not write this file; it names it so the integration can
|
|
472
|
+
* tell whether its own write can still reach the client. The env override is
|
|
473
|
+
* ZCode's own (`ZCODE_PERSONAL_PROVIDER_CONFIG_FILE`), so an operator who
|
|
474
|
+
* relocated the store is measured against the file their client actually opens
|
|
475
|
+
* rather than the default location. A relative override is refused for the same
|
|
476
|
+
* reason `ZCODE_DATA_DIR` refuses one: we and the client would disagree about
|
|
477
|
+
* which file it names, and here that disagreement decides whether an apply is
|
|
478
|
+
* reported as effective.
|
|
479
|
+
*/
|
|
480
|
+
export function zcodeProviderStorePath(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
481
|
+
const override = env.ZCODE_PERSONAL_PROVIDER_CONFIG_FILE?.trim();
|
|
482
|
+
if (override) return absoluteClientPath(override, home, "ZCODE_PERSONAL_PROVIDER_CONFIG_FILE");
|
|
483
|
+
return join(zcodeHomeDir(env, home), "v2", "provider_config.json");
|
|
484
|
+
}
|
|
485
|
+
|
|
450
486
|
/**
|
|
451
487
|
* Prime Agent resolves its agent directory from `PRIME_AGENT_CODING_AGENT_DIR`
|
|
452
488
|
* — the brand-derived spelling of the `PI_CODING_AGENT_DIR` that `ompAgentDir`
|
|
@@ -786,6 +786,58 @@ function catalogStatusFromProcesses(
|
|
|
786
786
|
return { state: stale ? "stale" : "fresh", processes: withStarts, catalogMtimeMs };
|
|
787
787
|
}
|
|
788
788
|
|
|
789
|
+
interface ComputedCodexAppServerCatalogStatus {
|
|
790
|
+
status: CodexAppServerCatalogStatus;
|
|
791
|
+
processes: CodexAppServerProcess[];
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Compute one catalog-state observation while retaining the command lines from the
|
|
796
|
+
* same process enumeration. The public collector deliberately exposes only the
|
|
797
|
+
* identity and timestamp projection; post-write warning code also needs the matched
|
|
798
|
+
* process records, and re-enumerating there creates a race between classification and
|
|
799
|
+
* reporting (as well as a second expensive Windows CIM walk).
|
|
800
|
+
*/
|
|
801
|
+
function computeCodexAppServerCatalogStatus(
|
|
802
|
+
io: CodexAppServerProcessIo,
|
|
803
|
+
): ComputedCodexAppServerCatalogStatus {
|
|
804
|
+
const platform = io.platform ?? process.platform;
|
|
805
|
+
const getuid = io.getuid ?? (() => {
|
|
806
|
+
try {
|
|
807
|
+
return typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
808
|
+
} catch {
|
|
809
|
+
return undefined;
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
let snapshots: ProcessSnapshot[];
|
|
813
|
+
let enumerationFailed = false;
|
|
814
|
+
const enumerate = io.listSnapshots ?? (() => defaultListSnapshots(platform, getuid));
|
|
815
|
+
try {
|
|
816
|
+
snapshots = enumerate();
|
|
817
|
+
} catch {
|
|
818
|
+
// A failed process read is unknown, never proof that nothing is running.
|
|
819
|
+
snapshots = [];
|
|
820
|
+
enumerationFailed = true;
|
|
821
|
+
}
|
|
822
|
+
const processes = codexAppServerProcessesFromSnapshots(snapshots);
|
|
823
|
+
if (processes.length === 0) {
|
|
824
|
+
return {
|
|
825
|
+
processes,
|
|
826
|
+
status: enumerationFailed
|
|
827
|
+
? { state: "unknown", processes: [], catalogMtimeMs: null }
|
|
828
|
+
: { state: "not_running", processes: [], catalogMtimeMs: null },
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
const catalogMtimeMs = (io.catalogMtimeMs ?? defaultCatalogMtimeMs)();
|
|
832
|
+
const starts = io.readStartMs
|
|
833
|
+
? new Map(processes.map(proc => [proc.pid, io.readStartMs!(proc.pid)] as const))
|
|
834
|
+
: readProcessStartMsBatch(processes.map(proc => proc.pid), platform);
|
|
835
|
+
return {
|
|
836
|
+
processes,
|
|
837
|
+
status: catalogStatusFromProcesses(processes, catalogMtimeMs, starts),
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
|
|
789
841
|
// Short TTL: process listing + stat run once per window even under per-turn
|
|
790
842
|
// guidance calls (#857).
|
|
791
843
|
let catalogStateCache: { atMs: number; status: CodexAppServerCatalogStatus } | null = null;
|
|
@@ -889,41 +941,7 @@ export function collectCodexAppServerCatalogState(
|
|
|
889
941
|
&& now - catalogStateCache.atMs < catalogStateTtlMs(catalogStateCache.status.state)) {
|
|
890
942
|
return catalogStateCache.status;
|
|
891
943
|
}
|
|
892
|
-
const
|
|
893
|
-
const platform = io.platform ?? process.platform;
|
|
894
|
-
const getuid = io.getuid ?? (() => {
|
|
895
|
-
try {
|
|
896
|
-
return typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
897
|
-
} catch {
|
|
898
|
-
return undefined;
|
|
899
|
-
}
|
|
900
|
-
});
|
|
901
|
-
let snapshots: ProcessSnapshot[];
|
|
902
|
-
let enumerationFailed = false;
|
|
903
|
-
const enumerate = io.listSnapshots ?? (() => defaultListSnapshots(platform, getuid));
|
|
904
|
-
try {
|
|
905
|
-
snapshots = enumerate();
|
|
906
|
-
} catch {
|
|
907
|
-
// Enumeration failure must never read as "nothing running" — that would let
|
|
908
|
-
// positive model guidance through on guesswork (#857). The injected seam gets
|
|
909
|
-
// the same contract as the default path: whoever enumerates, a failure to read
|
|
910
|
-
// the process list is unknown, not an empty machine.
|
|
911
|
-
snapshots = [];
|
|
912
|
-
enumerationFailed = true;
|
|
913
|
-
}
|
|
914
|
-
const processes = codexAppServerProcessesFromSnapshots(snapshots);
|
|
915
|
-
if (processes.length === 0) {
|
|
916
|
-
return enumerationFailed
|
|
917
|
-
? { state: "unknown", processes: [], catalogMtimeMs: null }
|
|
918
|
-
: { state: "not_running", processes: [], catalogMtimeMs: null };
|
|
919
|
-
}
|
|
920
|
-
const catalogMtimeMs = (io.catalogMtimeMs ?? defaultCatalogMtimeMs)();
|
|
921
|
-
const starts = io.readStartMs
|
|
922
|
-
? new Map(processes.map(proc => [proc.pid, io.readStartMs!(proc.pid)] as const))
|
|
923
|
-
: readProcessStartMsBatch(processes.map(proc => proc.pid), platform);
|
|
924
|
-
return catalogStatusFromProcesses(processes, catalogMtimeMs, starts);
|
|
925
|
-
};
|
|
926
|
-
const status = compute();
|
|
944
|
+
const status = computeCodexAppServerCatalogStatus(io).status;
|
|
927
945
|
if (fullyDefault) {
|
|
928
946
|
catalogStateCache = { atMs: now, status };
|
|
929
947
|
}
|
|
@@ -1228,16 +1246,30 @@ export function afterCatalogWriteHandleAppServers(
|
|
|
1228
1246
|
options: AfterCatalogWriteAppServerOptions,
|
|
1229
1247
|
): AfterCatalogWriteAppServerResult {
|
|
1230
1248
|
const excluded = new Set(options.excludePids ?? []);
|
|
1249
|
+
const hint = STALE_CODEX_APP_SERVER_HINT;
|
|
1250
|
+
if (!options.restart) {
|
|
1251
|
+
// A running process is not necessarily stale. Classify the exact process
|
|
1252
|
+
// enumeration that supplies the warning, and stay quiet when freshness cannot be
|
|
1253
|
+
// established rather than presenting an unknown observation as a known mismatch.
|
|
1254
|
+
const observed = computeCodexAppServerCatalogStatus(options.io ?? {});
|
|
1255
|
+
const starts = new Map(observed.status.processes.map(process => [process.pid, process.startedAtMs]));
|
|
1256
|
+
const catalogMtimeMs = observed.status.catalogMtimeMs;
|
|
1257
|
+
const processes = observed.processes.filter(process => !excluded.has(process.pid));
|
|
1258
|
+
const staleProcesses = catalogMtimeMs === null
|
|
1259
|
+
? []
|
|
1260
|
+
: processes.filter(process => {
|
|
1261
|
+
const startedAtMs = starts.get(process.pid);
|
|
1262
|
+
return startedAtMs !== null && startedAtMs !== undefined && startedAtMs <= catalogMtimeMs;
|
|
1263
|
+
});
|
|
1264
|
+
if (staleProcesses.length === 0) return { processes, warned: false, hint };
|
|
1265
|
+
options.log?.error(formatStaleCodexAppServerWarning(staleProcesses));
|
|
1266
|
+
return { processes: staleProcesses, warned: true, hint };
|
|
1267
|
+
}
|
|
1231
1268
|
const processes = listCodexAppServerProcesses(options.io)
|
|
1232
1269
|
.filter(process => !excluded.has(process.pid));
|
|
1233
|
-
const hint = STALE_CODEX_APP_SERVER_HINT;
|
|
1234
1270
|
if (processes.length === 0) {
|
|
1235
1271
|
return { processes, warned: false, hint };
|
|
1236
1272
|
}
|
|
1237
|
-
if (!options.restart) {
|
|
1238
|
-
options.log?.error(formatStaleCodexAppServerWarning(processes));
|
|
1239
|
-
return { processes, warned: true, hint };
|
|
1240
|
-
}
|
|
1241
1273
|
options.log?.log(
|
|
1242
1274
|
`Stopping Codex app-server process(es): ${processes.map(process => process.pid).join(", ")} `
|
|
1243
1275
|
+ "(active turns may be interrupted).",
|
|
@@ -226,9 +226,12 @@ export async function handleCodexAuthLoginStart(req: Request, config: OcxConfig,
|
|
|
226
226
|
// machine. Opening it on the hub host is useless at best, and on a
|
|
227
227
|
// headless host it fails. `deviceCode` is the same signal the generic
|
|
228
228
|
// OAuth login route uses to make this decision.
|
|
229
|
+
// Reported to the caller rather than discarded (#5261): a login whose browser never opened
|
|
230
|
+
// is indistinguishable from one that did, so it reads as success while nothing happens.
|
|
231
|
+
let browserLaunch: "started" | "failed" | "skipped" = "skipped";
|
|
229
232
|
if (result.url && !result.deviceCode && shouldOpenBrowserForLogin(body.openBrowser, runtimeConfig)) {
|
|
230
233
|
const { openUrl } = await import("../../lib/open-url");
|
|
231
|
-
openUrl(result.url);
|
|
234
|
+
browserLaunch = (await openUrl(result.url)).status === "started" ? "started" : "failed";
|
|
232
235
|
}
|
|
233
236
|
|
|
234
237
|
(async () => {
|
|
@@ -488,6 +491,8 @@ export async function handleCodexAuthLoginStart(req: Request, config: OcxConfig,
|
|
|
488
491
|
flowId,
|
|
489
492
|
url: result.url,
|
|
490
493
|
instructions: result.instructions,
|
|
494
|
+
// Never fatal: the URL is still a valid thing to open by hand, and the flow stays live.
|
|
495
|
+
browserLaunch,
|
|
491
496
|
// Dropped before #3366: every device-code surface renders this field,
|
|
492
497
|
// so withholding it left the GUI and CLI with no code to show.
|
|
493
498
|
...(result.deviceCode ? { deviceCode: result.deviceCode } : {}),
|
|
@@ -154,6 +154,34 @@ export function startupHealthSummary(health: StartupHealth): string {
|
|
|
154
154
|
return action ? `${summary}; ${action}` : summary;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* What to say at the end of setup, once routing is on disk and the autostart choice is made.
|
|
159
|
+
*
|
|
160
|
+
* #5261: applying the Codex integration does not install a background service, and on Windows
|
|
161
|
+
* the scheduled task that a separate install would create is logon-triggered rather than
|
|
162
|
+
* boot-triggered. So "routing written, nothing listening" is an ordinary state after a restart
|
|
163
|
+
* rather than a corruption — and the user is never told, because setup ends on a success line.
|
|
164
|
+
*
|
|
165
|
+
* A boot trigger is not the missing piece and would be a false reassurance. The task runs as
|
|
166
|
+
* the interactive user, so before logon there is no session for it to run in; making it truly
|
|
167
|
+
* pre-logon means a different principal and a different service backend, not another trigger.
|
|
168
|
+
* What is actually missing is that nobody says the dependency exists, which is cheap to fix and
|
|
169
|
+
* true on every platform.
|
|
170
|
+
*
|
|
171
|
+
* Reuses the existing health model rather than re-deriving the condition, so this cannot drift
|
|
172
|
+
* from what `ocx status` and `ocx doctor` report about the same install.
|
|
173
|
+
*/
|
|
174
|
+
export function injectedRoutingRestartWarningLines(health: StartupHealth): string[] {
|
|
175
|
+
if (health.status !== "at-risk") return [];
|
|
176
|
+
return [
|
|
177
|
+
// Deliberately not "nothing will restart the proxy": a healthy launcher shim does restart it,
|
|
178
|
+
// for CLI launches only, and is still at-risk. The summary line below says which case this is.
|
|
179
|
+
"⚠️ Codex routing is written to disk and survives a restart; keeping the proxy running is a separate matter.",
|
|
180
|
+
` ${startupHealthSummary(health)}`,
|
|
181
|
+
" While the proxy is down Codex cannot sign in or reach a model. 'ocx restore' undoes the routing without needing it.",
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
|
|
157
185
|
function classifyStartupHealthSummary(health: StartupHealth): string {
|
|
158
186
|
if (health.status === "native") return health.routingKind === "custom-remote"
|
|
159
187
|
? "custom remote Codex routing (no local restart dependency)"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CODEX_REASONING_LEVELS } from "../../reasoning-effort";
|
|
1
|
+
import { CODEX_REASONING_LEVELS, type CodexReasoningLevel } from "../../reasoning-effort";
|
|
2
2
|
import { clearModelCache } from "../model-cache";
|
|
3
3
|
import { routedSlug, slugEquivalenceKey } from "../../providers/slug-codec";
|
|
4
4
|
import { COMBO_NAMESPACE } from "../../combos";
|
|
@@ -872,7 +872,7 @@ export function mergeCatalogEntriesFromObservedState({
|
|
|
872
872
|
// (luna: no ultra) stay intact.
|
|
873
873
|
if (!freshCustomEntries.has(m) && !exactCombo && !reserveProjection && !String(e.slug ?? "").startsWith("opencode-go/")) {
|
|
874
874
|
const levels = Array.isArray(e.supported_reasoning_levels)
|
|
875
|
-
? e.supported_reasoning_levels as Array<
|
|
875
|
+
? e.supported_reasoning_levels as Array<Partial<CodexReasoningLevel>>
|
|
876
876
|
: [];
|
|
877
877
|
if (levels.length > 0
|
|
878
878
|
&& !suppressedSyntheticMaxSlugs.has(String(e.slug ?? ""))
|
|
@@ -8,7 +8,7 @@ import { clearModelCache, DEFAULT_MODEL_CACHE_TTL_MS, getFreshCached, getStaleCa
|
|
|
8
8
|
import { buildModelsRequest, resolveModelsAuthToken } from "../../oauth";
|
|
9
9
|
import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
10
10
|
import { modelInList } from "../../types";
|
|
11
|
-
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
11
|
+
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts, type CodexReasoningLevel } from "../../reasoning-effort";
|
|
12
12
|
import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata";
|
|
13
13
|
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
14
14
|
import { getProviderRegistryEntry } from "../../providers/registry";
|
|
@@ -286,7 +286,7 @@ export function isGpt56NativeSlug(slug: string): boolean {
|
|
|
286
286
|
|
|
287
287
|
export function ensureGpt56ReasoningLevels(entry: RawEntry): void {
|
|
288
288
|
const levels = Array.isArray(entry.supported_reasoning_levels)
|
|
289
|
-
? entry.supported_reasoning_levels as Array<
|
|
289
|
+
? entry.supported_reasoning_levels as Array<Partial<CodexReasoningLevel>>
|
|
290
290
|
: [];
|
|
291
291
|
const out = [...levels];
|
|
292
292
|
// max is a real native rung on the 5.6 family — always restored; ultra always advertised.
|
|
@@ -300,7 +300,7 @@ export function ensureGpt56ReasoningLevels(entry: RawEntry): void {
|
|
|
300
300
|
|
|
301
301
|
export function ensureUltraReasoningLevel(entry: RawEntry): void {
|
|
302
302
|
const levels = Array.isArray(entry.supported_reasoning_levels)
|
|
303
|
-
? entry.supported_reasoning_levels as Array<
|
|
303
|
+
? entry.supported_reasoning_levels as Array<Partial<CodexReasoningLevel>>
|
|
304
304
|
: [];
|
|
305
305
|
if (levels.length === 0) return;
|
|
306
306
|
const wanted = ["max", "ultra"];
|
|
@@ -52,7 +52,7 @@ import { routedSlug, slugEquals, slugEquivalenceKey, slugsEquivalent } from "../
|
|
|
52
52
|
import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
|
|
53
53
|
import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
|
|
54
54
|
import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
|
|
55
|
-
import { recordLiveCursorClaudeModels, recordLiveCursorMaxModeModels } from "../../adapters/cursor/catalog";
|
|
55
|
+
import { cursorLiveRosterScope, recordLiveCursorClaudeModels, recordLiveCursorMaxModeModels } from "../../adapters/cursor/catalog";
|
|
56
56
|
import { fetchQoderModels } from "../../adapters/qoder/live-models";
|
|
57
57
|
import { resolveQoderProfile } from "../../adapters/qoder/profiles";
|
|
58
58
|
import { fetchDevinUsableModels } from "../../adapters/devin/live-models";
|
|
@@ -289,15 +289,19 @@ export async function fetchProviderModelsWithAuth(
|
|
|
289
289
|
}
|
|
290
290
|
if (prov.adapter === "devin") {
|
|
291
291
|
if (!apiKey) return observed(configured, "degraded");
|
|
292
|
-
|
|
292
|
+
// Devin's usable-model list is entitlement-specific. Bind cache reads/writes to an
|
|
293
|
+
// irreversible credential fingerprint so a credential switch cannot observe another
|
|
294
|
+
// account's roster or stale fallback (the Qoder precedent above).
|
|
295
|
+
const authorityIdentity = createHash("sha256").update(apiKey).digest("hex");
|
|
296
|
+
const cachedDevin = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
|
|
293
297
|
if (cachedDevin) {
|
|
294
298
|
return observed(
|
|
295
299
|
withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedDevin)),
|
|
296
300
|
"authoritative",
|
|
297
301
|
);
|
|
298
302
|
}
|
|
299
|
-
if (isModelsFetchCoolingDown(name)) {
|
|
300
|
-
const cooling = getStaleCached(name);
|
|
303
|
+
if (isModelsFetchCoolingDown(name, undefined, undefined, authorityIdentity)) {
|
|
304
|
+
const cooling = getStaleCached(name, authorityIdentity);
|
|
301
305
|
return observed(
|
|
302
306
|
withConfiguredRetention(
|
|
303
307
|
cooling ? applyConfigHintsToCachedModels(name, prov, cooling) : configured,
|
|
@@ -339,17 +343,17 @@ export async function fetchProviderModelsWithAuth(
|
|
|
339
343
|
} as CatalogModel;
|
|
340
344
|
});
|
|
341
345
|
const forCache = withConfiguredRetention(result, { retainComboTargets: false });
|
|
342
|
-
if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
|
|
346
|
+
if (!setCached(name, forCache, Date.now(), cacheGeneration, authorityIdentity)) {
|
|
343
347
|
return observed(withConfiguredRetention(configured), "degraded");
|
|
344
348
|
}
|
|
345
349
|
markProviderDiscoveryOk(name, liveResult.models.length);
|
|
346
350
|
return observed(withConfiguredRetention(forCache), "authoritative");
|
|
347
351
|
}
|
|
348
352
|
if (isCurrentCacheGeneration()) {
|
|
349
|
-
markModelsFetchFailure(name);
|
|
353
|
+
markModelsFetchFailure(name, undefined, authorityIdentity);
|
|
350
354
|
markProviderDiscoveryFailed(name, { reason: liveResult.error === "auth" ? "provider" : "invalid_response" });
|
|
351
355
|
}
|
|
352
|
-
const stale = getStaleCached(name);
|
|
356
|
+
const stale = getStaleCached(name, authorityIdentity);
|
|
353
357
|
return observed(
|
|
354
358
|
withConfiguredRetention(stale ? applyConfigHintsToCachedModels(name, prov, stale) : configured),
|
|
355
359
|
"degraded",
|
|
@@ -361,15 +365,19 @@ export async function fetchProviderModelsWithAuth(
|
|
|
361
365
|
// variants this PLAN can use. Keep the base-model UX (the request builder appends the effort
|
|
362
366
|
// suffix) but filter the static seed to the bases the account actually has — so models not on the
|
|
363
367
|
// plan (e.g. claude-fable-5) drop out instead of failing ERROR_BAD_MODEL_NAME. Fall back to the seed.
|
|
364
|
-
|
|
368
|
+
// The roster is entitlement-specific, so the cache entry is bound to an irreversible
|
|
369
|
+
// credential fingerprint: a credential switch must not observe the previous account's
|
|
370
|
+
// plan roster, its stale fallback, or its failure cooldown suppression.
|
|
371
|
+
const authorityIdentity = createHash("sha256").update(apiKey).digest("hex");
|
|
372
|
+
const cachedCursor = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
|
|
365
373
|
if (cachedCursor) {
|
|
366
374
|
return observed(
|
|
367
375
|
withConfiguredRetention(applyConfigHintsToCachedModels(name, prov, cachedCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias)),
|
|
368
376
|
"authoritative",
|
|
369
377
|
);
|
|
370
378
|
}
|
|
371
|
-
if (isModelsFetchCoolingDown(name)) {
|
|
372
|
-
const cooling = getStaleCached(name);
|
|
379
|
+
if (isModelsFetchCoolingDown(name, undefined, undefined, authorityIdentity)) {
|
|
380
|
+
const cooling = getStaleCached(name, authorityIdentity);
|
|
373
381
|
return observed(
|
|
374
382
|
withConfiguredRetention(
|
|
375
383
|
cooling ? applyConfigHintsToCachedModels(name, prov, cooling, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
|
|
@@ -394,27 +402,28 @@ export async function fetchProviderModelsWithAuth(
|
|
|
394
402
|
// Cache the discovery-filtered roster without combo retention so a later
|
|
395
403
|
// gather can re-apply the current capture's retain set on read.
|
|
396
404
|
const forCache = withConfiguredRetention(result, { retainComboTargets: false });
|
|
397
|
-
if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
|
|
405
|
+
if (!setCached(name, forCache, Date.now(), cacheGeneration, authorityIdentity)) {
|
|
398
406
|
return observed(withConfiguredRetention(configured), "degraded");
|
|
399
407
|
}
|
|
400
408
|
// Publish roster-derived state only for a discovery the cache accepted: a stale
|
|
401
409
|
// in-flight capture (generation revoked by a credential/config change) must not
|
|
402
410
|
// overwrite the spelling or Max-Mode evidence of the newer one.
|
|
403
|
-
|
|
411
|
+
const liveRosterScope = { provider: name, key: cursorLiveRosterScope(prov.baseUrl, apiKey) };
|
|
412
|
+
recordLiveCursorClaudeModels(liveResult.models, liveRosterScope);
|
|
404
413
|
// Live Max-Mode evidence feeds the umbrella resolver's ultra gate
|
|
405
414
|
// (devlog 260828_cursor_umbrella_catalog; union with static evidence).
|
|
406
|
-
recordLiveCursorMaxModeModels(liveResult.maxModeModels ?? []);
|
|
415
|
+
recordLiveCursorMaxModeModels(liveResult.maxModeModels ?? [], liveRosterScope);
|
|
407
416
|
markProviderDiscoveryOk(name, liveResult.models.length);
|
|
408
417
|
return observed(withConfiguredRetention(forCache, { warnDrops: true }), "authoritative");
|
|
409
418
|
}
|
|
410
419
|
if (isCurrentCacheGeneration()) {
|
|
411
|
-
markModelsFetchFailure(name);
|
|
420
|
+
markModelsFetchFailure(name, undefined, authorityIdentity);
|
|
412
421
|
markProviderDiscoveryFailed(name, { reason: "provider" });
|
|
413
422
|
console.warn(
|
|
414
423
|
`[opencodex] Cursor model discovery for "${name}" failed [${liveResult.error}]${liveResult.detail ? `: ${liveResult.detail}` : ""}; using stale/static catalog degradation.`,
|
|
415
424
|
);
|
|
416
425
|
}
|
|
417
|
-
const staleCursor = getStaleCached(name);
|
|
426
|
+
const staleCursor = getStaleCached(name, authorityIdentity);
|
|
418
427
|
return observed(
|
|
419
428
|
withConfiguredRetention(
|
|
420
429
|
staleCursor ? applyConfigHintsToCachedModels(name, prov, staleCursor, undefined, metadataModelIdCaseFold, captured.effectiveAlias) : configured,
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
availableAccountGatedNativeModels,
|
|
16
16
|
codexModelEntitlementStateForAccount,
|
|
17
17
|
isCodexModelEntitlementSnapshotCurrent,
|
|
18
|
-
resolveCodexModelEntitlements,
|
|
19
18
|
type CodexModelEntitlementSnapshot,
|
|
20
19
|
} from "../model-entitlements";
|
|
20
|
+
import { resolveAdmittedCodexModelEntitlements } from "../model-entitlement-admission";
|
|
21
21
|
import { isAccountNeedsReauth } from "../account-runtime-state";
|
|
22
22
|
import { codexRuntimeStatePath } from "../runtime";
|
|
23
23
|
import {
|
|
@@ -601,7 +601,7 @@ export async function syncCatalogModels(
|
|
|
601
601
|
comboOmissions,
|
|
602
602
|
providerModelOutcomes,
|
|
603
603
|
}),
|
|
604
|
-
|
|
604
|
+
resolveAdmittedCodexModelEntitlements(config),
|
|
605
605
|
]);
|
|
606
606
|
const committed = withCatalogWriteSerialization(owningCodexHome, permit => {
|
|
607
607
|
// Desired state can flip OFF during the provider await above. The catalog
|
package/src/codex/convergence.ts
CHANGED
|
@@ -78,9 +78,9 @@ import {
|
|
|
78
78
|
availableAccountGatedNativeModels,
|
|
79
79
|
codexModelEntitlementStateForAccount,
|
|
80
80
|
isCodexModelEntitlementSnapshotCurrent,
|
|
81
|
-
resolveCodexModelEntitlements,
|
|
82
81
|
type CodexModelEntitlementSnapshot,
|
|
83
82
|
} from "./model-entitlements";
|
|
83
|
+
import { resolveAdmittedCodexModelEntitlements } from "./model-entitlement-admission";
|
|
84
84
|
import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS } from "./catalog/native-models";
|
|
85
85
|
import { providerCodexAccountMode } from "../providers/registry";
|
|
86
86
|
import { OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
@@ -434,7 +434,7 @@ export async function gatherCodexCatalogCandidate(
|
|
|
434
434
|
providerModelOutcomes,
|
|
435
435
|
discoveryPolicySnapshots: discoveryPolicies,
|
|
436
436
|
}),
|
|
437
|
-
|
|
437
|
+
resolveAdmittedCodexModelEntitlements(snapshot.config),
|
|
438
438
|
]);
|
|
439
439
|
const processLocal = processEvidence(source);
|
|
440
440
|
const sourceEvidence = sealCatalogGatherEvidenceSession(session);
|
|
@@ -335,6 +335,17 @@ class CodexHistoryIntegrityError extends Error {
|
|
|
335
335
|
*/
|
|
336
336
|
export const HISTORY_RELABEL_STANDS_DOWN = "history_paginated_requires_native_writer";
|
|
337
337
|
|
|
338
|
+
/**
|
|
339
|
+
* The narrower reason: a provider-table transition found an `openai`-tagged row Codex has
|
|
340
|
+
* already paginated. It is not a plain stand-down, because the transition also takes the root
|
|
341
|
+
* `openai_base_url` out, and that combination would send the conversation to Codex's built-in
|
|
342
|
+
* OpenAI endpoint rather than this proxy.
|
|
343
|
+
*
|
|
344
|
+
* A constant for the same reason as the one above: `src/codex/inject/paginated-openai-compat.ts`
|
|
345
|
+
* decides what to do about it, and a literal repeated in two files is how the pair drifts apart.
|
|
346
|
+
*/
|
|
347
|
+
export const HISTORY_PAGINATED_OPENAI_NEEDS_ROOT_OVERRIDE = "history_paginated_openai_requires_native_writer";
|
|
348
|
+
|
|
338
349
|
function assertLegacyHistoryRecord(line: string): void {
|
|
339
350
|
let value: unknown;
|
|
340
351
|
try { value = JSON.parse(line); } catch { throw new CodexHistoryIntegrityError("history_rollout_record_invalid"); }
|
|
@@ -459,7 +470,7 @@ export function preflightCodexHistoryInjection(
|
|
|
459
470
|
}
|
|
460
471
|
assertLegacyHistoryWritable(row.rollout_path);
|
|
461
472
|
}
|
|
462
|
-
if (foundPaginatedOpenaiRow) return
|
|
473
|
+
if (foundPaginatedOpenaiRow) return HISTORY_PAGINATED_OPENAI_NEEDS_ROOT_OVERRIDE;
|
|
463
474
|
return foundPaginatedRow ? HISTORY_RELABEL_STANDS_DOWN : null;
|
|
464
475
|
} catch (error) {
|
|
465
476
|
return error instanceof CodexHistoryIntegrityError
|