@bitkyc08/opencodex 2.55.0 → 2.57.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/bin/ocx.mjs +10 -0
- package/gui/dist/assets/{index-BBOZWGB6.css → index-C5-RdDmD.css} +1 -1
- package/gui/dist/assets/{index-VuoiWj9J.js → index-Cz7CLdif.js} +21 -21
- package/gui/dist/index.html +2 -2
- package/package.json +4 -3
- package/src/adapters/base.ts +21 -0
- package/src/adapters/codebuddy/adapter.ts +2 -1
- package/src/adapters/codebuddy/scaffold-guard.ts +248 -0
- package/src/adapters/command-code.ts +1 -1
- package/src/adapters/cursor/envelope-echo.ts +8 -2
- package/src/adapters/cursor/transport-retry.ts +46 -1
- package/src/adapters/cursor.ts +4 -0
- package/src/adapters/google.ts +7 -7
- package/src/adapters/kiro/adapter.ts +42 -1
- package/src/adapters/kiro/payload.ts +17 -3
- package/src/adapters/kiro/reasoning.ts +70 -7
- package/src/adapters/kiro/stream.ts +8 -2
- package/src/adapters/kiro/wire.ts +2 -1
- package/src/adapters/kiro-events.ts +21 -13
- package/src/adapters/kiro-retry.ts +23 -4
- package/src/adapters/openai-chat/errors.ts +116 -0
- package/src/adapters/openai-chat/messages.ts +346 -0
- package/src/adapters/openai-chat/passthrough.ts +146 -0
- package/src/adapters/openai-chat/response-events.ts +117 -0
- package/src/adapters/openai-chat/tool-call-validation.ts +200 -0
- package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
- package/src/adapters/openai-chat/tool-schema.ts +495 -0
- package/src/adapters/openai-chat/wire.ts +50 -0
- package/src/adapters/openai-chat.ts +40 -1452
- package/src/adapters/openai-responses/canonical-forward.ts +202 -0
- package/src/adapters/openai-responses/image-gen.ts +406 -0
- package/src/adapters/openai-responses/internal.ts +3 -0
- package/src/adapters/openai-responses/passthrough.ts +642 -0
- package/src/adapters/openai-responses/prompt-cache.ts +83 -0
- package/src/adapters/openai-responses/reasoning.ts +220 -0
- package/src/adapters/openai-responses/request-strips.ts +185 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +509 -0
- package/src/adapters/openai-responses/tool-schema.ts +293 -0
- package/src/adapters/openai-responses/web-search.ts +156 -0
- package/src/adapters/openai-responses.ts +4 -2625
- package/src/bridge/errors.ts +58 -0
- package/src/bridge/internal.ts +174 -0
- package/src/bridge/response-json.ts +630 -0
- package/src/bridge/sse.ts +1462 -0
- package/src/bridge.ts +5 -2204
- package/src/chat/inbound.ts +12 -1
- package/src/claude/desktop-profile.ts +66 -9
- package/src/claude/outbound.ts +18 -0
- package/src/cli/account-main.ts +1 -1
- package/src/cli/capabilities.ts +2 -2
- package/src/cli/combo.ts +10 -1
- package/src/cli/index.ts +48 -5
- package/src/cli/registry.ts +2 -1
- package/src/cli/system-command.ts +4 -4
- package/src/clients/config-export.ts +7 -3
- package/src/codex/account-label.ts +14 -3
- package/src/codex/account-lifecycle.ts +3 -0
- package/src/codex/account-store.ts +184 -35
- package/src/codex/account-usability.ts +21 -0
- package/src/codex/auth-api/account-list.ts +507 -0
- package/src/codex/auth-api/http.ts +32 -0
- package/src/codex/auth-api/login-flow.ts +566 -0
- package/src/codex/auth-api/login-state.ts +64 -0
- package/src/codex/auth-api/main-account-probe.ts +331 -0
- package/src/codex/auth-api/pool-mode-gate.ts +274 -0
- package/src/codex/auth-api/pool-quota-probe.ts +512 -0
- package/src/codex/auth-api/reset-credit-service.ts +431 -0
- package/src/codex/auth-api/routes.ts +425 -0
- package/src/codex/auth-api/runtime-config.ts +48 -0
- package/src/codex/auth-api.ts +27 -3118
- package/src/codex/auth-context.ts +252 -35
- package/src/codex/catalog/aggregation.ts +80 -1
- package/src/codex/catalog/auto-review.ts +507 -0
- package/src/codex/catalog/build-entries.ts +981 -0
- package/src/codex/catalog/combo-member.ts +375 -0
- package/src/codex/catalog/derive-entry.ts +229 -0
- package/src/codex/catalog/effort.ts +0 -1
- package/src/codex/catalog/gated-native-warn.ts +63 -0
- package/src/codex/catalog/gather-capture.ts +533 -0
- package/src/codex/catalog/model-hints.ts +691 -0
- package/src/codex/catalog/model-visibility.ts +305 -0
- package/src/codex/catalog/provider-fetch.ts +52 -2942
- package/src/codex/catalog/provider-models.ts +685 -0
- package/src/codex/catalog/remote.ts +30 -0
- package/src/codex/catalog/restore.ts +132 -0
- package/src/codex/catalog/retained-sync.ts +714 -0
- package/src/codex/catalog/routed-gather.ts +895 -0
- package/src/codex/catalog/subagent-roster.ts +176 -0
- package/src/codex/catalog/sync.ts +52 -2698
- package/src/codex/cli-install-provenance.ts +7 -1
- package/src/codex/convergence.ts +7 -2
- package/src/codex/desktop-app/types.ts +11 -2
- package/src/codex/desktop-app/windows.ts +5 -5
- package/src/codex/inject/config-toml.ts +563 -0
- package/src/codex/inject/remove.ts +192 -0
- package/src/codex/inject/restore.ts +567 -0
- package/src/codex/inject/routing-classify.ts +109 -0
- package/src/codex/inject/routing-target.ts +125 -0
- package/src/codex/inject.ts +89 -1444
- package/src/codex/lineage.ts +458 -0
- package/src/codex/model-entitlements.ts +152 -15
- package/src/codex/pool-refresh-backoff.ts +161 -0
- package/src/codex/quota-rejection.ts +104 -15
- package/src/codex/routing/active-account.ts +194 -0
- package/src/codex/routing/cache-affinity.ts +70 -0
- package/src/codex/routing/cooldown-math.ts +285 -0
- package/src/codex/routing/health-store.ts +402 -0
- package/src/codex/routing/probe-lease.ts +358 -0
- package/src/codex/routing/selection.ts +780 -0
- package/src/codex/routing/thread-affinity.ts +586 -0
- package/src/codex/routing/transient-hold-dispatch.ts +141 -0
- package/src/codex/routing.ts +370 -2271
- package/src/codex/shim-fingerprint.ts +223 -0
- package/src/codex/shim-inspect.ts +175 -0
- package/src/codex/shim-probe.ts +367 -0
- package/src/codex/shim-restore-lock.ts +169 -0
- package/src/codex/shim-state-file.ts +151 -0
- package/src/codex/shim-templates.ts +265 -0
- package/src/codex/shim.ts +48 -1268
- package/src/codex/warmup.ts +1 -1
- package/src/combos/failover.ts +85 -0
- package/src/combos/request.ts +17 -10
- package/src/combos/types.ts +23 -2
- package/src/config/diagnostics.ts +705 -0
- package/src/config/feature-flags.ts +55 -0
- package/src/config/live-reconcile.ts +403 -0
- package/src/config/load-degrade.ts +880 -0
- package/src/config/mutation-lock.ts +244 -0
- package/src/config/openai-tier-backup.ts +268 -0
- package/src/config/pending-teardown.ts +31 -0
- package/src/config/persist-unlocked.ts +92 -0
- package/src/config/proxy-env.ts +188 -0
- package/src/config/salvage.ts +244 -0
- package/src/config/schema/config-schema.ts +640 -0
- package/src/config/schema/leaf-validators.ts +855 -0
- package/src/config/warn-memo.ts +28 -0
- package/src/config.ts +234 -4481
- package/src/generated/compatibility-version.json +649 -121
- package/src/images/loop.ts +1 -1
- package/src/lib/errors.ts +17 -0
- package/src/lib/request-execution-budget.ts +198 -23
- package/src/lib/spend-reservation-ledger.ts +958 -0
- package/src/lib/state-store-registrations.ts +6 -2
- package/src/lib/test-home-guard.ts +85 -1
- package/src/lib/upstream-retry.ts +132 -21
- package/src/lib/windows-elevation.ts +76 -14
- package/src/lib/workflow-budget.ts +553 -30
- package/src/oauth/index.ts +2 -2
- package/src/oauth/key-providers.ts +2 -2
- package/src/providers/kiro-models.ts +4 -3
- package/src/providers/label.ts +19 -1
- package/src/providers/model-discovery.ts +16 -0
- package/src/providers/quota/account-cache.ts +441 -0
- package/src/providers/quota/antigravity.ts +295 -0
- package/src/providers/quota/report-cache.ts +320 -0
- package/src/providers/quota/vendor-probes-key.ts +1243 -0
- package/src/providers/quota/vendor-probes-oauth.ts +590 -0
- package/src/providers/quota.ts +324 -3079
- package/src/providers/registry/entries-core.ts +1228 -0
- package/src/providers/registry/entries-extended.ts +1213 -0
- package/src/providers/registry/model-seeds.ts +912 -0
- package/src/providers/registry/types.ts +352 -0
- package/src/providers/registry.ts +24 -3536
- package/src/responses/continuation-ownership.ts +29 -0
- package/src/responses/reasoning-envelope.ts +6 -3
- package/src/responses/state/replay-fingerprint.ts +80 -0
- package/src/responses/state/snapshot-codec.ts +104 -0
- package/src/responses/state/spill-failure.ts +118 -0
- package/src/responses/state/spill-queue.ts +665 -0
- package/src/responses/state/temp-recovery.ts +257 -0
- package/src/responses/state.ts +82 -1143
- package/src/routing/identity-domains.ts +456 -0
- package/src/routing/probe-lease.ts +613 -0
- package/src/server/chat-completions.ts +3 -1
- package/src/server/chat-native.ts +37 -9
- package/src/server/index/bounded-request.ts +88 -0
- package/src/server/index/live-sideband.ts +601 -0
- package/src/server/index/serve-options.ts +1766 -0
- package/src/server/index/startup-warnings.ts +213 -0
- package/src/server/index/websocket-handler.ts +339 -0
- package/src/server/index.ts +45 -2552
- package/src/server/inspection-tee.ts +107 -0
- package/src/server/live.ts +46 -1
- package/src/server/management/combo-routes.ts +10 -1
- package/src/server/management/route-registry.ts +26 -23
- package/src/server/management/shared.ts +8 -5
- package/src/server/management/workflow-budget-routes.ts +133 -0
- package/src/server/management-api.ts +12 -0
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +14 -19
- package/src/server/request-log-conversation.ts +9 -7
- package/src/server/request-log.ts +372 -4
- package/src/server/response-log-body.ts +153 -0
- package/src/server/responses/account-change-state.ts +307 -0
- package/src/server/responses/adapter-continuation.ts +540 -0
- package/src/server/responses/adapter-delivery.ts +208 -0
- package/src/server/responses/adapter-dispatch.ts +1042 -0
- package/src/server/responses/codex-ws-wire.ts +5 -0
- package/src/server/responses/collaboration.ts +74 -4
- package/src/server/responses/combo-session-recall.ts +68 -8
- package/src/server/responses/compact.ts +113 -17
- package/src/server/responses/completion-policy.ts +33 -0
- package/src/server/responses/core-auth.ts +529 -0
- package/src/server/responses/core-codex-account.ts +907 -0
- package/src/server/responses/core-combo-failure.ts +210 -0
- package/src/server/responses/core-combo.ts +787 -0
- package/src/server/responses/core-errors.ts +170 -0
- package/src/server/responses/core-lifetime.ts +95 -0
- package/src/server/responses/core-normalize.ts +350 -0
- package/src/server/responses/core-opaque-recovery.ts +380 -0
- package/src/server/responses/core-options.ts +159 -0
- package/src/server/responses/core-replay.ts +298 -0
- package/src/server/responses/core.ts +192 -8893
- package/src/server/responses/encrypted-payload.ts +0 -1
- package/src/server/responses/input-admission.ts +126 -6
- package/src/server/responses/passthrough-delivery.ts +869 -0
- package/src/server/responses/passthrough-dispatch.ts +1494 -0
- package/src/server/responses/passthrough-error.ts +38 -2
- package/src/server/responses/passthrough-execution.ts +54 -0
- package/src/server/responses/request-prepare.ts +1080 -0
- package/src/server/responses/request-send-budget.ts +259 -0
- package/src/server/responses/request-sidecar-auth.ts +149 -0
- package/src/server/responses/request-spend.ts +147 -0
- package/src/server/responses/request-transport.ts +803 -0
- package/src/server/responses/response-effects.ts +157 -0
- package/src/server/responses/run-turn-execution.ts +476 -0
- package/src/server/responses/sidecar-execution.ts +463 -0
- package/src/server/responses/terminal-guard.ts +65 -4
- package/src/server/responses-image-gen-repair.ts +1 -1
- package/src/server/responses-undeclared-tool-guard.ts +9 -5
- package/src/server/workflow-refusal.ts +84 -0
- package/src/service/windows-ops.ts +210 -16
- package/src/service/windows-scheduler.ts +28 -21
- package/src/service.ts +1 -1
- package/src/types/config.ts +34 -1
- package/src/types/request.ts +8 -5
- package/src/types/tools.ts +24 -0
- package/src/types.ts +2 -0
- package/src/update/index.ts +10 -0
- package/src/update/stop-contract.d.mts +1 -0
- package/src/update/stop-contract.mjs +19 -0
- package/src/update/stop-decision.d.mts +1 -1
- package/src/update/stop-decision.mjs +12 -3
- package/src/usage/log.ts +147 -1
- package/src/usage/summary.ts +171 -21
- package/src/vision/anthropic-describe.ts +1 -1
- package/src/vision/describe.ts +5 -5
- package/src/web-search/anthropic-executor.ts +1 -1
- package/src/web-search/exa-executor.ts +1 -1
- package/src/web-search/executor.ts +1 -1
- package/src/web-search/gemini-executor.ts +1 -1
- package/src/web-search/loop.ts +1 -1
- package/src/web-search/ollama-executor.ts +1 -1
- package/src/web-search/parse.ts +67 -14
- package/src/web-search/passthrough-bridge.ts +64 -31
- package/src/web-search/xai-executor.ts +1 -1
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import { redactSecretString } from "../../lib/redact";
|
|
2
|
+
import type { OcxConfig } from "../../types";
|
|
3
|
+
import { encodeRoutedModelId } from "../../providers/slug-codec";
|
|
4
|
+
import { canonicalAutoReviewModelKey, isValidAutoReviewModel as isValidAutoReviewTarget } from "../../config/provider-validation";
|
|
5
|
+
import { readConfiguredAutoReviewModel } from "./parsing";
|
|
6
|
+
import type { RawEntry } from "./parsing";
|
|
7
|
+
import { configuredCatalogEntry } from "./subagent-roster";
|
|
8
|
+
|
|
9
|
+
const AUTO_REVIEW_ROOT_MARKER = "opencodex_auto_review_root";
|
|
10
|
+
|
|
11
|
+
interface RootAutoReviewStamp {
|
|
12
|
+
slug: string;
|
|
13
|
+
original: string | null;
|
|
14
|
+
applied: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function rootAutoReviewStamp(entry: RawEntry): RootAutoReviewStamp | undefined {
|
|
18
|
+
const value = entry[AUTO_REVIEW_ROOT_MARKER];
|
|
19
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
20
|
+
const stamp = value as Record<string, unknown>;
|
|
21
|
+
if (stamp.slug !== entry.slug || typeof stamp.slug !== "string"
|
|
22
|
+
|| typeof stamp.applied !== "string"
|
|
23
|
+
|| (stamp.original !== null && typeof stamp.original !== "string")) return undefined;
|
|
24
|
+
return stamp as unknown as RootAutoReviewStamp;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/** True when the value is a valid Codex catalog auto-review selector. */
|
|
29
|
+
export function isValidAutoReviewModel(value: unknown): value is string {
|
|
30
|
+
return isValidAutoReviewTarget(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type AutoReviewModelOverrideResult = "absent" | "applied" | "invalid" | "unresolved";
|
|
34
|
+
|
|
35
|
+
/** True when a catalog row was synthesized by opencodex instead of coming from upstream. */
|
|
36
|
+
function isRoutedCatalogEntry(entry: RawEntry): boolean {
|
|
37
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
38
|
+
return slug.includes("/")
|
|
39
|
+
|| (typeof entry.description === "string" && entry.description.startsWith("Routed via opencodex → "));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Restore an owned native value, retaining provenance to avoid legacy reclassification. */
|
|
43
|
+
function clearAutoReviewOverrideValue(entry: RawEntry): void {
|
|
44
|
+
const stamp = rootAutoReviewStamp(entry);
|
|
45
|
+
if (stamp) {
|
|
46
|
+
if (entry.auto_review_model_override === stamp.applied) entry.auto_review_model_override = stamp.original;
|
|
47
|
+
} else {
|
|
48
|
+
entry.auto_review_model_override = null;
|
|
49
|
+
delete entry[AUTO_REVIEW_ROOT_MARKER];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Legacy whole-catalog root stamp: releases before AUTO_REVIEW_ROOT_MARKER wrote root stamps that
|
|
55
|
+
* are textually identical to an upstream value, so the only way to recognize one is the uniform
|
|
56
|
+
* signature the no-provider path relies on — a single value that a routed row also carries.
|
|
57
|
+
* Returns the stamped values when the observed rows match that shape.
|
|
58
|
+
*/
|
|
59
|
+
function legacyRootStampValues(observedModels: readonly RawEntry[]): ReadonlySet<string> | undefined {
|
|
60
|
+
if (observedModels.some(entry => entry?.[AUTO_REVIEW_ROOT_MARKER] !== undefined)) return undefined;
|
|
61
|
+
const configuredValues = new Set(observedModels.flatMap(entry => {
|
|
62
|
+
const value = entry?.auto_review_model_override;
|
|
63
|
+
return typeof value === "string" && value.trim() ? [value] : [];
|
|
64
|
+
}));
|
|
65
|
+
const globalStamp = configuredValues.size === 1
|
|
66
|
+
&& observedModels.some(entry => {
|
|
67
|
+
const value = entry.auto_review_model_override;
|
|
68
|
+
return isRoutedCatalogEntry(entry)
|
|
69
|
+
&& typeof value === "string"
|
|
70
|
+
&& value.trim().length > 0
|
|
71
|
+
&& configuredValues.has(value);
|
|
72
|
+
})
|
|
73
|
+
&& observedModels.every(entry => {
|
|
74
|
+
const value = entry?.auto_review_model_override;
|
|
75
|
+
return value === null
|
|
76
|
+
|| value === undefined
|
|
77
|
+
|| (typeof value === "string" && configuredValues.has(value));
|
|
78
|
+
});
|
|
79
|
+
return globalStamp ? configuredValues : undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Sweep legacy root stamps off the rows a root removal owns, before provider plans land.
|
|
84
|
+
*
|
|
85
|
+
* Root removal reaches marker-tagged native rows on its own, but a catalog written before the
|
|
86
|
+
* marker only carries the legacy signature — and provider stamping rewrites that signature before
|
|
87
|
+
* the root pass could read it, so the sweep has to run first.
|
|
88
|
+
*/
|
|
89
|
+
function clearLegacyRootStamps(models: readonly RawEntry[], sourceModels: readonly RawEntry[] = []): void {
|
|
90
|
+
const legacyStamp = legacyRootStampValues([...models, ...sourceModels]);
|
|
91
|
+
if (legacyStamp === undefined) return;
|
|
92
|
+
for (const entry of models) {
|
|
93
|
+
if (!entry || typeof entry !== "object") continue;
|
|
94
|
+
const current = entry.auto_review_model_override;
|
|
95
|
+
if (entry[AUTO_REVIEW_ROOT_MARKER] === undefined
|
|
96
|
+
&& typeof current === "string" && legacyStamp.has(current)) clearAutoReviewOverrideValue(entry);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Clear the root selector from every row this path owns: routed rows, rows stamped by a release
|
|
102
|
+
* that writes the provenance marker, and the legacy whole-catalog stamp that predates it.
|
|
103
|
+
*/
|
|
104
|
+
function clearAutoReviewModelOverride(
|
|
105
|
+
models: readonly RawEntry[],
|
|
106
|
+
sourceModels: readonly RawEntry[] = [],
|
|
107
|
+
): void {
|
|
108
|
+
const legacyStamp = legacyRootStampValues([...models, ...sourceModels]);
|
|
109
|
+
for (const entry of models) {
|
|
110
|
+
if (!entry || typeof entry !== "object") continue;
|
|
111
|
+
const current = entry.auto_review_model_override;
|
|
112
|
+
if (isRoutedCatalogEntry(entry)
|
|
113
|
+
|| (entry[AUTO_REVIEW_ROOT_MARKER] === true || rootAutoReviewStamp(entry) !== undefined)
|
|
114
|
+
|| (legacyStamp !== undefined && typeof current === "string" && legacyStamp.has(current))) {
|
|
115
|
+
clearAutoReviewOverrideValue(entry);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Warn once about a malformed or unresolvable root auto-review selector. */
|
|
121
|
+
function warnAutoReviewModelDiagnostic(
|
|
122
|
+
reason: "invalid" | "unresolved",
|
|
123
|
+
configured: string,
|
|
124
|
+
): void {
|
|
125
|
+
const safeConfigured = JSON.stringify(redactSecretString(configured));
|
|
126
|
+
const detail = reason === "unresolved"
|
|
127
|
+
? "the selector was not found in the final catalog"
|
|
128
|
+
: "the selector format is invalid";
|
|
129
|
+
console.warn(
|
|
130
|
+
`[opencodex] auto_review_model ${detail} (${safeConfigured}); preserving normal upstream auto-review behavior.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Warn once about a malformed or unresolvable provider-scoped auto-review selector. */
|
|
135
|
+
function warnProviderAutoReviewModelDiagnostic(
|
|
136
|
+
reason: "invalid" | "unresolved",
|
|
137
|
+
provider: string,
|
|
138
|
+
configured: string,
|
|
139
|
+
): void {
|
|
140
|
+
const safeProvider = JSON.stringify(redactSecretString(provider));
|
|
141
|
+
const safeConfigured = JSON.stringify(redactSecretString(configured));
|
|
142
|
+
const detail = reason === "unresolved"
|
|
143
|
+
? "the selector was not found in the final catalog"
|
|
144
|
+
: "the selector format is invalid";
|
|
145
|
+
console.warn(
|
|
146
|
+
`[opencodex] auto_review_model for provider ${safeProvider} ${detail} (${safeConfigured}); using the next valid provider/root selector or upstream behavior.`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Note once when a bare selector resolves to a row outside the provider it was configured on.
|
|
152
|
+
*
|
|
153
|
+
* That is how a native model is named as a reviewer, so it stays usable, but a mistyped target must
|
|
154
|
+
* not be silent: the operator sees which catalog row actually supplies the reviewer.
|
|
155
|
+
*/
|
|
156
|
+
function warnProviderAutoReviewForeignTarget(provider: string, configured: string, target: string): void {
|
|
157
|
+
const safeProvider = JSON.stringify(redactSecretString(provider));
|
|
158
|
+
const safeConfigured = JSON.stringify(redactSecretString(configured));
|
|
159
|
+
const safeTarget = JSON.stringify(redactSecretString(target));
|
|
160
|
+
console.warn(
|
|
161
|
+
`[opencodex] auto_review_model for provider ${safeProvider} (${safeConfigured}) resolved to ${safeTarget}, which is not a row of that provider; that catalog row supplies the reviewer.`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Preserve native upstream overrides and the root-derived provenance marker from source rows. */
|
|
166
|
+
function preserveNativeAutoReviewModelOverrides(
|
|
167
|
+
models: readonly RawEntry[],
|
|
168
|
+
sourceModels: readonly RawEntry[],
|
|
169
|
+
): void {
|
|
170
|
+
const existing = new Map<string, { value: string | null; root: true | RootAutoReviewStamp | undefined }>();
|
|
171
|
+
for (const entry of sourceModels) {
|
|
172
|
+
const slug = typeof entry.slug === "string" ? entry.slug : undefined;
|
|
173
|
+
const value = entry.auto_review_model_override;
|
|
174
|
+
if (!slug || isRoutedCatalogEntry(entry)) continue;
|
|
175
|
+
if (typeof value === "string" || value === null) {
|
|
176
|
+
existing.set(slug, { value, root: rootAutoReviewStamp(entry) ?? (entry[AUTO_REVIEW_ROOT_MARKER] === true ? true : undefined) });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
for (const entry of models) {
|
|
180
|
+
const slug = typeof entry.slug === "string" ? entry.slug : undefined;
|
|
181
|
+
if (!slug || isRoutedCatalogEntry(entry) || !existing.has(slug)) continue;
|
|
182
|
+
const saved = existing.get(slug)!;
|
|
183
|
+
entry.auto_review_model_override = saved.value;
|
|
184
|
+
if (saved.root) entry[AUTO_REVIEW_ROOT_MARKER] = structuredClone(saved.root);
|
|
185
|
+
else delete entry[AUTO_REVIEW_ROOT_MARKER];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Stamp a root-derived override and mark native rows so later root removal is durable. */
|
|
190
|
+
function stampRootAutoReviewOverride(entry: RawEntry, target: string): void {
|
|
191
|
+
if (!isRoutedCatalogEntry(entry)) {
|
|
192
|
+
const previous = rootAutoReviewStamp(entry);
|
|
193
|
+
const current = entry.auto_review_model_override;
|
|
194
|
+
entry[AUTO_REVIEW_ROOT_MARKER] = {
|
|
195
|
+
slug: typeof entry.slug === "string" ? entry.slug : "",
|
|
196
|
+
original: previous && current === previous.applied
|
|
197
|
+
? previous.original : typeof current === "string" ? current : null,
|
|
198
|
+
applied: target,
|
|
199
|
+
} satisfies RootAutoReviewStamp;
|
|
200
|
+
} else {
|
|
201
|
+
delete entry[AUTO_REVIEW_ROOT_MARKER];
|
|
202
|
+
}
|
|
203
|
+
entry.auto_review_model_override = target;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Stamp a provider-derived override; provider stamps never fall under root removal. */
|
|
207
|
+
function stampProviderAutoReviewOverride(entry: RawEntry, target: string): void {
|
|
208
|
+
entry.auto_review_model_override = target;
|
|
209
|
+
delete entry[AUTO_REVIEW_ROOT_MARKER];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Apply the root Codex auto-review selector to every catalog row, or clear it when the value is
|
|
214
|
+
* absent, blank, malformed, or does not resolve against the assembled catalog.
|
|
215
|
+
*/
|
|
216
|
+
export function applyAutoReviewModelOverride(
|
|
217
|
+
models: RawEntry[] | undefined,
|
|
218
|
+
autoReviewModel: string | null | undefined,
|
|
219
|
+
sourceModels: readonly RawEntry[] = [],
|
|
220
|
+
): AutoReviewModelOverrideResult {
|
|
221
|
+
if (!models || !Array.isArray(models)) return "absent";
|
|
222
|
+
if (autoReviewModel === null || autoReviewModel === undefined) {
|
|
223
|
+
clearAutoReviewModelOverride(models, sourceModels);
|
|
224
|
+
return "absent";
|
|
225
|
+
}
|
|
226
|
+
const trimmed = autoReviewModel.trim();
|
|
227
|
+
if (!trimmed) {
|
|
228
|
+
clearAutoReviewModelOverride(models, sourceModels);
|
|
229
|
+
return "absent";
|
|
230
|
+
}
|
|
231
|
+
if (!isValidAutoReviewModel(trimmed)) {
|
|
232
|
+
clearAutoReviewModelOverride(models, sourceModels);
|
|
233
|
+
warnAutoReviewModelDiagnostic("invalid", trimmed);
|
|
234
|
+
return "invalid";
|
|
235
|
+
}
|
|
236
|
+
if (!configuredCatalogEntry(models, trimmed)) {
|
|
237
|
+
clearAutoReviewModelOverride(models, sourceModels);
|
|
238
|
+
warnAutoReviewModelDiagnostic("unresolved", trimmed);
|
|
239
|
+
return "unresolved";
|
|
240
|
+
}
|
|
241
|
+
for (const entry of models) {
|
|
242
|
+
if (entry && typeof entry === "object") {
|
|
243
|
+
stampRootAutoReviewOverride(entry, trimmed);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return "applied";
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Validated provider-scoped target with both the configured spelling and catalog slug. */
|
|
250
|
+
interface ValidProviderReviewTarget {
|
|
251
|
+
configured: string;
|
|
252
|
+
target: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** One provider's resolved provider-wide and per-model auto-review targets. */
|
|
256
|
+
interface ProviderReviewPlan {
|
|
257
|
+
wide?: ValidProviderReviewTarget;
|
|
258
|
+
perModel: Map<string, ValidProviderReviewTarget>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Public provider namespace of a routed catalog row, when it has one. */
|
|
262
|
+
function catalogEntryProviderName(entry: RawEntry): string | undefined {
|
|
263
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
264
|
+
const slash = slug.indexOf("/");
|
|
265
|
+
return slash > 0 && isRoutedCatalogEntry(entry) ? slug.slice(0, slash) : undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** Encoded model-id segment of a routed catalog row, when it has one. */
|
|
269
|
+
function catalogEntryModelSegment(entry: RawEntry): string | undefined {
|
|
270
|
+
const slug = typeof entry.slug === "string" ? entry.slug : "";
|
|
271
|
+
const slash = slug.indexOf("/");
|
|
272
|
+
return slash > 0 ? slug.slice(slash + 1) : undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Case-preserving encoded key used to match per-model override maps. */
|
|
276
|
+
function providerModelKey(modelId: string): string {
|
|
277
|
+
return canonicalAutoReviewModelKey(modelId);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* True when another routed row of this provider already carries `alias` as its own model id.
|
|
282
|
+
*
|
|
283
|
+
* The alias API validates against whatever ids discovery has reported so far, so on a cold start an
|
|
284
|
+
* alias can be persisted that later turns out to name a different row. A key using it is then not
|
|
285
|
+
* an alternate spelling of the aliased model — it is that row's id — and must not be propagated.
|
|
286
|
+
*/
|
|
287
|
+
function aliasNamesAnotherRoutedRow(models: readonly RawEntry[], provider: string, alias: string): boolean {
|
|
288
|
+
const encoded = encodeRoutedModelId(alias);
|
|
289
|
+
return models.some(entry => isRoutedCatalogEntry(entry)
|
|
290
|
+
&& catalogEntryProviderName(entry) === provider
|
|
291
|
+
&& catalogEntryModelSegment(entry) === encoded);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Resolve one configured target against the assembled catalog; bare values name a model of the same provider. */
|
|
295
|
+
function resolveProviderReviewTarget(
|
|
296
|
+
models: readonly RawEntry[],
|
|
297
|
+
provider: string,
|
|
298
|
+
configuredRaw: unknown,
|
|
299
|
+
): { kind: "valid"; value: ValidProviderReviewTarget; foreign?: boolean } | { kind: "invalid"; configured: string } | { kind: "unresolved"; configured: string } | { kind: "absent" } {
|
|
300
|
+
if (typeof configuredRaw !== "string") return { kind: "absent" };
|
|
301
|
+
const configured = configuredRaw.trim();
|
|
302
|
+
if (!configured) return { kind: "absent" };
|
|
303
|
+
if (!isValidAutoReviewModel(configured)) return { kind: "invalid", configured };
|
|
304
|
+
const prefix = `${provider}/`;
|
|
305
|
+
let match: RawEntry | undefined;
|
|
306
|
+
const sameProviderCandidate = (rawModelId: string): RawEntry | undefined => models.find(entry => {
|
|
307
|
+
if (!isRoutedCatalogEntry(entry) || typeof entry.slug !== "string" || !entry.slug.startsWith(prefix)) return false;
|
|
308
|
+
const segment = catalogEntryModelSegment(entry);
|
|
309
|
+
return segment !== undefined && segment === encodeRoutedModelId(rawModelId);
|
|
310
|
+
});
|
|
311
|
+
// A bare selector names a model of this provider. A full selector that resolves in the
|
|
312
|
+
// assembled catalog already names the exact row, including a same-provider encoded slug.
|
|
313
|
+
if (!configured.includes("/")) {
|
|
314
|
+
match = sameProviderCandidate(configured);
|
|
315
|
+
}
|
|
316
|
+
match ??= configuredCatalogEntry(models, configured);
|
|
317
|
+
if (!match && configured.startsWith(prefix)) {
|
|
318
|
+
match = sameProviderCandidate(configured.slice(prefix.length));
|
|
319
|
+
}
|
|
320
|
+
if (!match) {
|
|
321
|
+
// A raw model id may itself contain "/" (for example zenmux moonshotai/kimi-k3).
|
|
322
|
+
// After the full-selector lookup misses, try that spelling as a same-provider id.
|
|
323
|
+
match = sameProviderCandidate(configured);
|
|
324
|
+
}
|
|
325
|
+
if (!match) return { kind: "unresolved", configured };
|
|
326
|
+
const target = typeof match.slug === "string" ? match.slug : configured;
|
|
327
|
+
// A qualified selector may name another provider's row on purpose; only a bare value that lands
|
|
328
|
+
// outside this provider is worth reporting.
|
|
329
|
+
const foreign = !configured.includes("/") && catalogEntryProviderName(match) !== provider;
|
|
330
|
+
return { kind: "valid", value: { configured, target }, ...(foreign ? { foreign: true } : {}) };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Build resolved per-provider plans and emit one diagnostic per bad selector. */
|
|
334
|
+
function buildProviderReviewPlans(
|
|
335
|
+
models: readonly RawEntry[],
|
|
336
|
+
config: Pick<OcxConfig, "providers">,
|
|
337
|
+
): { plans: Map<string, ProviderReviewPlan>; failure?: "invalid" | "unresolved" } {
|
|
338
|
+
const plans = new Map<string, ProviderReviewPlan>();
|
|
339
|
+
let failure: "invalid" | "unresolved" | undefined;
|
|
340
|
+
const warned = new Set<string>();
|
|
341
|
+
const recordFailure = (kind: "invalid" | "unresolved", provider: string, configured: string): void => {
|
|
342
|
+
const signature = `${provider}\u0000${configured}`;
|
|
343
|
+
if (warned.has(signature)) return;
|
|
344
|
+
warned.add(signature);
|
|
345
|
+
warnProviderAutoReviewModelDiagnostic(kind, provider, configured);
|
|
346
|
+
failure ??= kind;
|
|
347
|
+
};
|
|
348
|
+
const recordForeignTarget = (provider: string, configured: string, target: string): void => {
|
|
349
|
+
const signature = `${provider}\u0000foreign\u0000${configured}`;
|
|
350
|
+
if (warned.has(signature)) return;
|
|
351
|
+
warned.add(signature);
|
|
352
|
+
warnProviderAutoReviewForeignTarget(provider, configured, target);
|
|
353
|
+
};
|
|
354
|
+
for (const [name, provider] of Object.entries(config.providers ?? {})) {
|
|
355
|
+
if (provider.autoReviewModel === undefined && provider.autoReviewModelOverrides === undefined) continue;
|
|
356
|
+
const plan: ProviderReviewPlan = { perModel: new Map() };
|
|
357
|
+
if (provider.autoReviewModel !== undefined) {
|
|
358
|
+
const resolved = resolveProviderReviewTarget(models, name, provider.autoReviewModel);
|
|
359
|
+
if (resolved.kind === "valid") {
|
|
360
|
+
plan.wide = resolved.value;
|
|
361
|
+
if (resolved.foreign) recordForeignTarget(name, resolved.value.configured, resolved.value.target);
|
|
362
|
+
}
|
|
363
|
+
else if (resolved.kind !== "absent") recordFailure(resolved.kind, name, resolved.configured);
|
|
364
|
+
}
|
|
365
|
+
if (provider.autoReviewModelOverrides !== undefined) {
|
|
366
|
+
for (const [modelId, rawTarget] of Object.entries(provider.autoReviewModelOverrides)) {
|
|
367
|
+
const resolved = resolveProviderReviewTarget(models, name, rawTarget);
|
|
368
|
+
if (resolved.kind === "valid") {
|
|
369
|
+
plan.perModel.set(providerModelKey(modelId), resolved.value);
|
|
370
|
+
if (resolved.foreign) recordForeignTarget(name, resolved.value.configured, resolved.value.target);
|
|
371
|
+
} else if (resolved.kind !== "absent") {
|
|
372
|
+
recordFailure(resolved.kind, name, resolved.configured);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// `modelAliases` publishes a second public name for a model id, and a routed row's slug always
|
|
377
|
+
// carries the upstream id — so accept an override key written in either spelling.
|
|
378
|
+
for (const [modelId, alias] of Object.entries(provider.modelAliases ?? {})) {
|
|
379
|
+
if (typeof alias !== "string" || !alias.trim()) continue;
|
|
380
|
+
if (aliasNamesAnotherRoutedRow(models, name, alias)) continue;
|
|
381
|
+
const idKey = providerModelKey(modelId);
|
|
382
|
+
const aliasKey = providerModelKey(alias);
|
|
383
|
+
if (idKey === aliasKey) continue;
|
|
384
|
+
const fromId = plan.perModel.get(idKey);
|
|
385
|
+
const fromAlias = plan.perModel.get(aliasKey);
|
|
386
|
+
if (fromId !== undefined && fromAlias === undefined) plan.perModel.set(aliasKey, fromId);
|
|
387
|
+
else if (fromAlias !== undefined && fromId === undefined) plan.perModel.set(idKey, fromAlias);
|
|
388
|
+
}
|
|
389
|
+
if (plan.wide !== undefined || plan.perModel.size > 0) plans.set(name, plan);
|
|
390
|
+
}
|
|
391
|
+
return { plans, failure };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Apply or clear the root selector only on rows without a provider stamp. */
|
|
395
|
+
function applyRootSelectorToRemaining(
|
|
396
|
+
models: readonly RawEntry[],
|
|
397
|
+
rootValue: string | null | undefined,
|
|
398
|
+
providerStamped: ReadonlySet<RawEntry>,
|
|
399
|
+
): AutoReviewModelOverrideResult {
|
|
400
|
+
const clearRemaining = (): void => {
|
|
401
|
+
for (const entry of models) {
|
|
402
|
+
if (!entry || providerStamped.has(entry)) continue;
|
|
403
|
+
// Native rows written by releases before the root marker cannot be told apart from upstream
|
|
404
|
+
// values once provider stamps diverge. clearLegacyRootStamps sweeps the ones the legacy
|
|
405
|
+
// uniform signature still recognizes before provider plans land, because provider stamping
|
|
406
|
+
// destroys that signature; a catalog that no longer matches it needs a one-off manual sync.
|
|
407
|
+
if (isRoutedCatalogEntry(entry) || entry[AUTO_REVIEW_ROOT_MARKER] === true || rootAutoReviewStamp(entry)) clearAutoReviewOverrideValue(entry);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
if (rootValue === null || rootValue === undefined) {
|
|
411
|
+
clearRemaining();
|
|
412
|
+
return "absent";
|
|
413
|
+
}
|
|
414
|
+
const trimmed = rootValue.trim();
|
|
415
|
+
if (!trimmed) {
|
|
416
|
+
clearRemaining();
|
|
417
|
+
return "absent";
|
|
418
|
+
}
|
|
419
|
+
if (!isValidAutoReviewModel(trimmed)) {
|
|
420
|
+
clearRemaining();
|
|
421
|
+
warnAutoReviewModelDiagnostic("invalid", trimmed);
|
|
422
|
+
return "invalid";
|
|
423
|
+
}
|
|
424
|
+
if (!configuredCatalogEntry(models, trimmed)) {
|
|
425
|
+
clearRemaining();
|
|
426
|
+
warnAutoReviewModelDiagnostic("unresolved", trimmed);
|
|
427
|
+
return "unresolved";
|
|
428
|
+
}
|
|
429
|
+
for (const entry of models) {
|
|
430
|
+
if (!entry || providerStamped.has(entry)) continue;
|
|
431
|
+
stampRootAutoReviewOverride(entry, trimmed);
|
|
432
|
+
}
|
|
433
|
+
return "applied";
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Provider-aware variant: provider rows win and the root selector is the fallback. */
|
|
437
|
+
export function applyConfiguredAutoReviewModelOverride(
|
|
438
|
+
models: RawEntry[] | undefined,
|
|
439
|
+
rootAutoReviewModel: string | null | undefined,
|
|
440
|
+
config: Pick<OcxConfig, "providers">,
|
|
441
|
+
sourceModels: readonly RawEntry[] = [],
|
|
442
|
+
): AutoReviewModelOverrideResult {
|
|
443
|
+
if (!models || !Array.isArray(models)) return "absent";
|
|
444
|
+
// Runs unconditionally because the sweep only fires on the uniform legacy signature. A resolved
|
|
445
|
+
// root selector restamps every row it touches below, so the call is behavior-preserving there;
|
|
446
|
+
// with the root absent, invalid, or unresolved those clears are final — which is the point, and
|
|
447
|
+
// also the limit: the legacy heuristic cannot tell a root stamp from an identical upstream value.
|
|
448
|
+
clearLegacyRootStamps(models, sourceModels);
|
|
449
|
+
const { plans, failure } = buildProviderReviewPlans(models, config);
|
|
450
|
+
const providerStamped = new Set<RawEntry>();
|
|
451
|
+
for (const entry of models) {
|
|
452
|
+
if (!entry || typeof entry !== "object") continue;
|
|
453
|
+
const provider = catalogEntryProviderName(entry);
|
|
454
|
+
if (!provider) continue;
|
|
455
|
+
const plan = plans.get(provider);
|
|
456
|
+
if (!plan) continue;
|
|
457
|
+
const modelSegment = catalogEntryModelSegment(entry);
|
|
458
|
+
const perModel = modelSegment === undefined ? undefined : plan.perModel.get(providerModelKey(modelSegment));
|
|
459
|
+
const selected = perModel ?? plan.wide;
|
|
460
|
+
if (!selected) continue;
|
|
461
|
+
stampProviderAutoReviewOverride(entry, selected.target);
|
|
462
|
+
providerStamped.add(entry);
|
|
463
|
+
}
|
|
464
|
+
const rootResult = applyRootSelectorToRemaining(models, rootAutoReviewModel, providerStamped);
|
|
465
|
+
const providerApplied = [...providerStamped].some(entry => typeof entry.auto_review_model_override === "string");
|
|
466
|
+
if (providerApplied) {
|
|
467
|
+
if (rootResult === "invalid" || rootResult === "unresolved") return rootResult;
|
|
468
|
+
return failure ?? "applied";
|
|
469
|
+
}
|
|
470
|
+
return failure ?? rootResult;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** True when any provider row configures a provider-scoped auto-review selector. */
|
|
474
|
+
function configHasProviderAutoReview(config: Pick<OcxConfig, "providers">): boolean {
|
|
475
|
+
return Object.values(config.providers ?? {}).some(provider =>
|
|
476
|
+
provider.autoReviewModel !== undefined || provider.autoReviewModelOverrides !== undefined);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Apply the root Codex auto-review selector after the final catalog merge. */
|
|
480
|
+
export function finalizeAutoReviewModelOverride(
|
|
481
|
+
models: RawEntry[] | undefined,
|
|
482
|
+
sourceModels: readonly RawEntry[] = [],
|
|
483
|
+
config?: Pick<OcxConfig, "providers">,
|
|
484
|
+
): AutoReviewModelOverrideResult {
|
|
485
|
+
if (models && sourceModels.length > 0) preserveNativeAutoReviewModelOverrides(models, sourceModels);
|
|
486
|
+
if (config && configHasProviderAutoReview(config)) {
|
|
487
|
+
return applyConfiguredAutoReviewModelOverride(models, readConfiguredAutoReviewModel(), config, sourceModels);
|
|
488
|
+
}
|
|
489
|
+
return applyAutoReviewModelOverride(models, readConfiguredAutoReviewModel(), sourceModels);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Why an account-gated native model stopped being offered, but only when the answer is one the
|
|
493
|
+
* operator can act on.
|
|
494
|
+
*
|
|
495
|
+
* Suppression is an omission: the row is never built, so there is no catalog entry for a reason
|
|
496
|
+
* to ride on and no downstream consumer that could explain it later. #4212's reporter watched
|
|
497
|
+
* their models disappear and reasonably concluded the proxy was broken, because every surface
|
|
498
|
+
* that changed said nothing about the account that caused it.
|
|
499
|
+
*
|
|
500
|
+
* Returns `undefined` for the ordinary case — an account that is simply not entitled to a gated
|
|
501
|
+
* model. That is the default state for most installations, it is not news, and warning about it
|
|
502
|
+
* on every sync would bury the one case that matters. A credential the operator must repair is
|
|
503
|
+
* the case that matters, so that is the only one this speaks up about.
|
|
504
|
+
*
|
|
505
|
+
* Accounts are named with the durable `p`-prefixed log label, the same identifier the dashboard
|
|
506
|
+
* shows, never the raw pool id or the email.
|
|
507
|
+
*/
|