@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
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// Everything else in the row is left alone.
|
|
20
20
|
|
|
21
21
|
import { PROVIDER_REGISTRY } from "./registry";
|
|
22
|
+
import { MODEL_KEYED_RECORDS, MODEL_ID_LISTS, MODEL_ID_SCALARS, MODEL_NESTED_RECORDS } from "./model-rename-fields";
|
|
22
23
|
import { providerConfigSeed } from "./derive";
|
|
23
24
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
24
25
|
|
|
@@ -45,6 +46,52 @@ export interface ModelRename {
|
|
|
45
46
|
* but lose the reasoning picker entirely.
|
|
46
47
|
*/
|
|
47
48
|
dropReasoningEffortMap?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Remove both the retired id and its replacement from `noReasoningModels`.
|
|
51
|
+
*
|
|
52
|
+
* Use this only when the rename also marks a capability change: carrying the old
|
|
53
|
+
* no-reasoning classification onto a newly adjustable alias would keep the picker
|
|
54
|
+
* disabled after the model id itself was repaired.
|
|
55
|
+
*/
|
|
56
|
+
dropNoReasoningModels?: boolean;
|
|
57
|
+
/** Refresh exact registry defaults already saved under the replacement id. */
|
|
58
|
+
targetSeedRefresh?: {
|
|
59
|
+
contextWindow?: { from: number; to: number };
|
|
60
|
+
reasoning?: {
|
|
61
|
+
fromEfforts: readonly string[];
|
|
62
|
+
toEfforts: readonly string[];
|
|
63
|
+
defaultEffort: string;
|
|
64
|
+
effortMap: Readonly<Record<string, string>>;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const KIMI_K28_ALIAS = "kimi-for-coding";
|
|
70
|
+
const KIMI_RETIRED_CODING_IDS = [
|
|
71
|
+
"kimi-k2.7-code",
|
|
72
|
+
"kimi-k2.7-code-highspeed",
|
|
73
|
+
"kimi-k2.6",
|
|
74
|
+
"kimi-k2.5",
|
|
75
|
+
] as const;
|
|
76
|
+
const KIMI_K28_TARGET_SEED_REFRESH: NonNullable<ModelRename["targetSeedRefresh"]> = {
|
|
77
|
+
contextWindow: { from: 262_144, to: 1_048_576 },
|
|
78
|
+
reasoning: {
|
|
79
|
+
fromEfforts: [],
|
|
80
|
+
toEfforts: ["low", "high", "max"],
|
|
81
|
+
defaultEffort: "max",
|
|
82
|
+
effortMap: { none: "none", low: "low", medium: "high", high: "high", xhigh: "max", max: "max" },
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function kimiCodingRenames(provider: "kimi" | "kimi-code", endpoint: string): ModelRename[] {
|
|
87
|
+
return KIMI_RETIRED_CODING_IDS.map(from => ({
|
|
88
|
+
provider,
|
|
89
|
+
from,
|
|
90
|
+
to: KIMI_K28_ALIAS,
|
|
91
|
+
reason: `Moonshot retired the k2.x coding ids from the ${endpoint}; kimi-for-coding is the stable alias the endpoint still serves`,
|
|
92
|
+
dropNoReasoningModels: true,
|
|
93
|
+
targetSeedRefresh: KIMI_K28_TARGET_SEED_REFRESH,
|
|
94
|
+
}));
|
|
48
95
|
}
|
|
49
96
|
|
|
50
97
|
/**
|
|
@@ -65,6 +112,14 @@ export const MODEL_RENAMES: readonly ModelRename[] = [
|
|
|
65
112
|
to: "qwen3.8-max",
|
|
66
113
|
reason: "Alibaba shipped Qwen3.8-Max as stable and documents the preview endpoint as liable to be taken offline once preview concludes",
|
|
67
114
|
},
|
|
115
|
+
// Kimi coding renames. Moonshot retired the k2.x ids from the subscription/coding
|
|
116
|
+
// endpoint when K2.8 Preview shipped (live /coding/v1/models lists only
|
|
117
|
+
// kimi-for-coding[-highspeed], k3, k3-256k); kimi-for-coding is the stable alias the
|
|
118
|
+
// endpoint still serves and currently routes to K2.8 Preview. The registry picker no
|
|
119
|
+
// longer seeds the retired ids, so a saved defaultModel naming one is a dead selection
|
|
120
|
+
// rather than a merely outdated one.
|
|
121
|
+
...kimiCodingRenames("kimi", "subscription endpoint after K2.8 Preview shipped"),
|
|
122
|
+
...kimiCodingRenames("kimi-code", "coding endpoint after K2.8 Preview shipped"),
|
|
68
123
|
// Antigravity Flash generations. Google takes the previous Flash model off Cloud Code
|
|
69
124
|
// Assist almost immediately when the next ships, so a saved 3.6 (or older 3.5) id is a
|
|
70
125
|
// dead selection rather than a merely outdated one. Routing already redirects these ids
|
|
@@ -83,40 +138,6 @@ export const MODEL_RENAMES: readonly ModelRename[] = [
|
|
|
83
138
|
})),
|
|
84
139
|
];
|
|
85
140
|
|
|
86
|
-
/** Provider fields that key metadata by model id. */
|
|
87
|
-
const MODEL_KEYED_RECORDS = [
|
|
88
|
-
"modelContextWindows",
|
|
89
|
-
"modelMaxOutputTokens",
|
|
90
|
-
"modelInputModalities",
|
|
91
|
-
"modelReasoningEfforts",
|
|
92
|
-
"modelSuppressSyntheticMax",
|
|
93
|
-
"modelDefaultReasoningEfforts",
|
|
94
|
-
"modelReasoningEffortMap",
|
|
95
|
-
] as const;
|
|
96
|
-
|
|
97
|
-
/** Provider fields that are flat lists of model ids. */
|
|
98
|
-
const MODEL_ID_LISTS = [
|
|
99
|
-
"models",
|
|
100
|
-
// A retired id left here is worse than a stale label: `filterCatalogModels` treats
|
|
101
|
-
// `selectedModels` as an exact-match allowlist, so a user who allowlisted only the
|
|
102
|
-
// retired model gets NO replacement row at all — the model silently vanishes from
|
|
103
|
-
// their catalog instead of being renamed. OAuth reconciliation does not cover this
|
|
104
|
-
// field, so the rename has to.
|
|
105
|
-
"selectedModels",
|
|
106
|
-
// Same reasoning as `selectedModels`: a retired id pinned here would be resurrected as a
|
|
107
|
-
// ghost row on every discovery instead of following the rename (#1690).
|
|
108
|
-
"retainModels",
|
|
109
|
-
"noVisionModels",
|
|
110
|
-
"noReasoningModels",
|
|
111
|
-
"noTemperatureModels",
|
|
112
|
-
"noTopPModels",
|
|
113
|
-
"noPenaltyModels",
|
|
114
|
-
"autoToolChoiceOnlyModels",
|
|
115
|
-
"preserveReasoningContentModels",
|
|
116
|
-
"thinkingBudgetModels",
|
|
117
|
-
"directReasoningEffortModels",
|
|
118
|
-
] as const;
|
|
119
|
-
|
|
120
141
|
function renameInList(value: unknown, from: string, to: string): string[] | null {
|
|
121
142
|
if (!Array.isArray(value) || !value.includes(from)) return null;
|
|
122
143
|
const seen = new Set<string>();
|
|
@@ -133,6 +154,18 @@ function renameInList(value: unknown, from: string, to: string): string[] | null
|
|
|
133
154
|
return next;
|
|
134
155
|
}
|
|
135
156
|
|
|
157
|
+
function dropRenamedIdsFromList(
|
|
158
|
+
value: unknown,
|
|
159
|
+
from: string,
|
|
160
|
+
to: string,
|
|
161
|
+
dropStaleTarget: boolean,
|
|
162
|
+
): string[] | null {
|
|
163
|
+
if (!Array.isArray(value)) return null;
|
|
164
|
+
const hasRetired = value.includes(from);
|
|
165
|
+
if (!hasRetired && !(dropStaleTarget && value.includes(to))) return null;
|
|
166
|
+
return value.filter(entry => typeof entry === "string" && entry !== from && entry !== to);
|
|
167
|
+
}
|
|
168
|
+
|
|
136
169
|
function renameInRecord(value: unknown, from: string, to: string): Record<string, unknown> | null {
|
|
137
170
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
138
171
|
const record = value as Record<string, unknown>;
|
|
@@ -160,6 +193,42 @@ function dropFromRecord(value: unknown, from: string): Record<string, unknown> |
|
|
|
160
193
|
return next;
|
|
161
194
|
}
|
|
162
195
|
|
|
196
|
+
function sameStringArray(value: unknown, expected: readonly string[]): boolean {
|
|
197
|
+
return Array.isArray(value)
|
|
198
|
+
&& value.length === expected.length
|
|
199
|
+
&& value.every((entry, index) => entry === expected[index]);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function targetReasoningMatchesStaleSeed(row: Record<string, unknown>, rename: ModelRename): boolean {
|
|
203
|
+
const reasoning = rename.targetSeedRefresh?.reasoning;
|
|
204
|
+
const efforts = row.modelReasoningEfforts as Record<string, unknown> | undefined;
|
|
205
|
+
return !!reasoning && !!efforts && sameStringArray(efforts[rename.to], reasoning.fromEfforts);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Refresh only exact defaults emitted by the previous registry; preserve user overrides. */
|
|
209
|
+
function refreshTargetSeed(row: Record<string, unknown>, rename: ModelRename): boolean {
|
|
210
|
+
const refresh = rename.targetSeedRefresh;
|
|
211
|
+
if (!refresh) return false;
|
|
212
|
+
let changed = false;
|
|
213
|
+
|
|
214
|
+
const windows = row.modelContextWindows as Record<string, unknown> | undefined;
|
|
215
|
+
if (windows && refresh.contextWindow && windows[rename.to] === refresh.contextWindow.from) {
|
|
216
|
+
windows[rename.to] = refresh.contextWindow.to;
|
|
217
|
+
changed = true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const reasoning = refresh.reasoning;
|
|
221
|
+
const efforts = row.modelReasoningEfforts as Record<string, unknown> | undefined;
|
|
222
|
+
if (!reasoning || !efforts || !sameStringArray(efforts[rename.to], reasoning.fromEfforts)) return changed;
|
|
223
|
+
efforts[rename.to] = [...reasoning.toEfforts];
|
|
224
|
+
|
|
225
|
+
const defaults = (row.modelDefaultReasoningEfforts ??= {}) as Record<string, unknown>;
|
|
226
|
+
if (!(rename.to in defaults)) defaults[rename.to] = reasoning.defaultEffort;
|
|
227
|
+
const maps = (row.modelReasoningEffortMap ??= {}) as Record<string, unknown>;
|
|
228
|
+
if (!(rename.to in maps)) maps[rename.to] = { ...reasoning.effortMap };
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
163
232
|
/**
|
|
164
233
|
* `provider/model` rows in the top-level `disabledModels` list.
|
|
165
234
|
*
|
|
@@ -281,7 +350,14 @@ export function projectModelRenames(
|
|
|
281
350
|
let touched = false;
|
|
282
351
|
for (const field of MODEL_ID_LISTS) {
|
|
283
352
|
if (isRegistryResidue(seed, field, row[field], rename)) continue;
|
|
284
|
-
const next =
|
|
353
|
+
const next = rename.dropNoReasoningModels && field === "noReasoningModels"
|
|
354
|
+
? dropRenamedIdsFromList(
|
|
355
|
+
row[field],
|
|
356
|
+
rename.from,
|
|
357
|
+
rename.to,
|
|
358
|
+
targetReasoningMatchesStaleSeed(row, rename),
|
|
359
|
+
)
|
|
360
|
+
: renameInList(row[field], rename.from, rename.to);
|
|
285
361
|
if (!next) continue;
|
|
286
362
|
row[field] = next;
|
|
287
363
|
touched = true;
|
|
@@ -295,11 +371,22 @@ export function projectModelRenames(
|
|
|
295
371
|
row[field] = next;
|
|
296
372
|
touched = true;
|
|
297
373
|
}
|
|
298
|
-
|
|
299
|
-
|
|
374
|
+
for (const field of MODEL_NESTED_RECORDS) {
|
|
375
|
+
const container = row[field];
|
|
376
|
+
if (!container || typeof container !== "object" || Array.isArray(container)) continue;
|
|
377
|
+
const nested = container as Record<string, unknown>;
|
|
378
|
+
const next = renameInRecord(nested.models, rename.from, rename.to);
|
|
379
|
+
if (!next) continue;
|
|
380
|
+
row[field] = { ...nested, models: next };
|
|
381
|
+
touched = true;
|
|
382
|
+
}
|
|
383
|
+
for (const field of MODEL_ID_SCALARS) {
|
|
384
|
+
if (row[field] !== rename.from) continue;
|
|
385
|
+
row[field] = rename.to;
|
|
300
386
|
touched = true;
|
|
301
387
|
}
|
|
302
388
|
if (renameDisabledModels(config, rename)) touched = true;
|
|
389
|
+
if (touched && refreshTargetSeed(row, rename)) touched = true;
|
|
303
390
|
|
|
304
391
|
if (touched) {
|
|
305
392
|
changed = true;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { resolveProviderApiKey } from "../key-store";
|
|
2
2
|
import { getProviderRegistryEntry, registryEntryForProviderDestination } from "../registry";
|
|
3
3
|
import { isCanonicalOllamaCloudUrl } from "../../adapters/ollama-native-url";
|
|
4
|
+
import { providerEgressFetchInit, resolveProviderEgress } from "../../lib/provider-egress";
|
|
5
|
+
import { configuredOutboundFetch } from "../../lib/proxy-env";
|
|
4
6
|
import { QUOTA_JSON_READ_FAILURE, asRecord, normalizePercent, normalizeResetAt, readQuotaJson, REQUEST_TIMEOUT_MS, toFiniteNumber } from "../quota-wire";
|
|
5
7
|
import {
|
|
6
8
|
AUTHORITATIVE_EMPTY_QUOTA,
|
|
@@ -15,6 +17,12 @@ import { getTokenForAccountQuotaProbe } from "./account-cache";
|
|
|
15
17
|
import type { AccountQuotaMode, ProviderQuota, ProviderQuotaCreditsUsd } from "../quota-types";
|
|
16
18
|
import type { OcxProviderConfig } from "../../types";
|
|
17
19
|
|
|
20
|
+
// A quota probe must use the provider's inference route so it neither reports a false healthy path nor leaks a key through another exit.
|
|
21
|
+
async function quotaFetch(providerName: string, config: OcxProviderConfig, url: string, init: RequestInit): Promise<Response> {
|
|
22
|
+
const egress = resolveProviderEgress({ providerName, provider: config, url });
|
|
23
|
+
return configuredOutboundFetch(url, { ...init, ...providerEgressFetchInit(egress) });
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
const KIMI_CODE_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
19
27
|
const KIMI_CODE_USAGE_URL = `${KIMI_CODE_BASE_URL}/usages`;
|
|
20
28
|
const COMMAND_CODE_BASE_URL = "https://api.commandcode.ai";
|
|
@@ -144,10 +152,10 @@ async function fetchA6apiQuota(provider: string, config: OcxProviderConfig): Pro
|
|
|
144
152
|
if (!apiKey) return null;
|
|
145
153
|
const headers = { Accept: "application/json", Authorization: `Bearer ${apiKey}` } as const;
|
|
146
154
|
const [subscriptionResponse, tokenResponse] = await Promise.all([
|
|
147
|
-
|
|
155
|
+
quotaFetch(provider, config, `${A6API_BASE_URL}/dashboard/billing/subscription`, {
|
|
148
156
|
headers, redirect: "error", signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
149
157
|
}),
|
|
150
|
-
|
|
158
|
+
quotaFetch(provider, config, `${A6API_BASE_URL}/api/usage/token/`, {
|
|
151
159
|
headers, redirect: "error", signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
152
160
|
}),
|
|
153
161
|
]);
|
|
@@ -241,7 +249,7 @@ async function fetchOpenCodeGoQuota(provider: string, config: OcxProviderConfig)
|
|
|
241
249
|
if (!isCanonicalOpenCodeGoBaseUrl(config.baseUrl)) return null;
|
|
242
250
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
243
251
|
if (!apiKey) return null;
|
|
244
|
-
const response = await
|
|
252
|
+
const response = await quotaFetch(provider, config, OPENCODE_GO_USAGE_URL, {
|
|
245
253
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
246
254
|
redirect: "error",
|
|
247
255
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -287,7 +295,7 @@ async function fetchOpenRouterQuota(provider: string, config: OcxProviderConfig)
|
|
|
287
295
|
if (!isCanonicalOpenRouterBaseUrl(config.baseUrl)) return null;
|
|
288
296
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
289
297
|
if (!apiKey) return null;
|
|
290
|
-
const response = await
|
|
298
|
+
const response = await quotaFetch(provider, config, `${OPENROUTER_BASE_URL}/key`, {
|
|
291
299
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
292
300
|
redirect: "error",
|
|
293
301
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -337,7 +345,7 @@ async function fetchDeepSeekQuota(provider: string, config: OcxProviderConfig):
|
|
|
337
345
|
if (!isCanonicalDeepSeekBaseUrl(config.baseUrl)) return null;
|
|
338
346
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
339
347
|
if (!apiKey) return null;
|
|
340
|
-
const response = await
|
|
348
|
+
const response = await quotaFetch(provider, config, `${DEEPSEEK_BASE_URL}/user/balance`, {
|
|
341
349
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
342
350
|
redirect: "error",
|
|
343
351
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -382,7 +390,7 @@ async function fetchClineQuota(provider: string, config: OcxProviderConfig): Pro
|
|
|
382
390
|
if (!isCanonicalClineBaseUrl(config.baseUrl)) return null;
|
|
383
391
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
384
392
|
if (!apiKey) return null;
|
|
385
|
-
const response = await
|
|
393
|
+
const response = await quotaFetch(provider, config, `${CLINE_BASE_URL}/api/v1/users/me/plan/usage-limits`, {
|
|
386
394
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
387
395
|
redirect: "error",
|
|
388
396
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -480,7 +488,7 @@ async function fetchOllamaCloudQuota(provider: string, config: OcxProviderConfig
|
|
|
480
488
|
if (!isCanonicalOllamaCloudBaseUrl(effectiveBaseUrl)) return null;
|
|
481
489
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
482
490
|
if (!apiKey) return null;
|
|
483
|
-
const response = await
|
|
491
|
+
const response = await quotaFetch(provider, config, OLLAMA_CLOUD_USAGE_URL, {
|
|
484
492
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
485
493
|
redirect: "error",
|
|
486
494
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -604,7 +612,7 @@ async function fetchZaiQuota(provider: string, config: OcxProviderConfig): Promi
|
|
|
604
612
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
605
613
|
if (!apiKey) return null;
|
|
606
614
|
const authorization = monitorHost === ZAI_CN_BASE_URL ? apiKey : `Bearer ${apiKey}`;
|
|
607
|
-
const response = await
|
|
615
|
+
const response = await quotaFetch(provider, config, `${monitorHost}/api/monitor/usage/quota/limit`, {
|
|
608
616
|
headers: { Accept: "application/json", Authorization: authorization },
|
|
609
617
|
redirect: "error",
|
|
610
618
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -652,7 +660,7 @@ async function fetchMinimaxQuota(provider: string, config: OcxProviderConfig): P
|
|
|
652
660
|
if (!apiKey) return null;
|
|
653
661
|
const cnHost = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.minimaxi.com");
|
|
654
662
|
const remainsUrl = cnHost ? "https://api.minimaxi.com/v1/token_plan/remains" : MINIMAX_REMAINS_URL;
|
|
655
|
-
const response = await
|
|
663
|
+
const response = await quotaFetch(provider, config, remainsUrl, {
|
|
656
664
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
657
665
|
redirect: "error",
|
|
658
666
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -695,7 +703,7 @@ async function fetchMoonshotQuota(provider: string, config: OcxProviderConfig):
|
|
|
695
703
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
696
704
|
if (!apiKey) return null;
|
|
697
705
|
const host = normalizedBaseUrl(config.baseUrl)?.startsWith("https://api.moonshot.cn") ? "https://api.moonshot.cn/v1" : MOONSHOT_BASE_URL;
|
|
698
|
-
const response = await
|
|
706
|
+
const response = await quotaFetch(provider, config, `${host}/users/me/balance`, {
|
|
699
707
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
700
708
|
redirect: "error",
|
|
701
709
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -738,7 +746,7 @@ async function fetchVeniceQuota(provider: string, config: OcxProviderConfig): Pr
|
|
|
738
746
|
if (!isCanonicalVeniceBaseUrl(config.baseUrl)) return null;
|
|
739
747
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
740
748
|
if (!apiKey) return null;
|
|
741
|
-
const response = await
|
|
749
|
+
const response = await quotaFetch(provider, config, `${VENICE_BASE_URL}/billing/balance`, {
|
|
742
750
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
743
751
|
redirect: "error",
|
|
744
752
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -781,7 +789,7 @@ async function fetchSyntheticQuota(provider: string, config: OcxProviderConfig):
|
|
|
781
789
|
if (!isCanonicalSyntheticBaseUrl(config.baseUrl)) return null;
|
|
782
790
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
783
791
|
if (!apiKey) return null;
|
|
784
|
-
const response = await
|
|
792
|
+
const response = await quotaFetch(provider, config, `${SYNTHETIC_BASE_URL}/quotas`, {
|
|
785
793
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
786
794
|
redirect: "error",
|
|
787
795
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -831,7 +839,7 @@ async function fetchDeepInfraQuota(provider: string, config: OcxProviderConfig):
|
|
|
831
839
|
if (!isCanonicalDeepInfraBaseUrl(config.baseUrl)) return null;
|
|
832
840
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
833
841
|
if (!apiKey) return null;
|
|
834
|
-
const response = await
|
|
842
|
+
const response = await quotaFetch(provider, config, `${DEEPINFRA_BASE_URL}/payment/checklist?compute_owed=true`, {
|
|
835
843
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
836
844
|
redirect: "error",
|
|
837
845
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -873,7 +881,7 @@ async function fetchNeuralwattQuota(provider: string, config: OcxProviderConfig)
|
|
|
873
881
|
if (!isCanonicalNeuralwattBaseUrl(config.baseUrl)) return null;
|
|
874
882
|
const apiKey = resolveProviderApiKey(config.apiKey)?.trim();
|
|
875
883
|
if (!apiKey) return null;
|
|
876
|
-
const response = await
|
|
884
|
+
const response = await quotaFetch(provider, config, `${NEURALWATT_BASE_URL}/quota`, {
|
|
877
885
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
878
886
|
redirect: "error",
|
|
879
887
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -1050,7 +1058,7 @@ export async function fetchKimiQuota(provider: string, config: OcxProviderConfig
|
|
|
1050
1058
|
// Never release credentials to a user-edited or lookalike provider host.
|
|
1051
1059
|
if (!isCanonicalKimiCodeBaseUrl(config.baseUrl)) return null;
|
|
1052
1060
|
if (!accessToken) return null;
|
|
1053
|
-
const response = await
|
|
1061
|
+
const response = await quotaFetch(provider, config, KIMI_CODE_USAGE_URL, {
|
|
1054
1062
|
headers: { Accept: "application/json", Authorization: `Bearer ${accessToken}` },
|
|
1055
1063
|
redirect: "error",
|
|
1056
1064
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -1077,9 +1085,14 @@ function parseCommandCodeWindow(value: unknown): { percent: number; resetAt?: nu
|
|
|
1077
1085
|
}
|
|
1078
1086
|
|
|
1079
1087
|
/** Soft-fail GET returning a parsed record, or null when unavailable. */
|
|
1080
|
-
async function fetchCommandCodeJson(
|
|
1088
|
+
async function fetchCommandCodeJson(
|
|
1089
|
+
provider: string,
|
|
1090
|
+
config: OcxProviderConfig,
|
|
1091
|
+
url: string,
|
|
1092
|
+
bearer: string,
|
|
1093
|
+
): Promise<Record<string, unknown> | null> {
|
|
1081
1094
|
try {
|
|
1082
|
-
const response = await
|
|
1095
|
+
const response = await quotaFetch(provider, config, url, {
|
|
1083
1096
|
headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` },
|
|
1084
1097
|
redirect: "error",
|
|
1085
1098
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -1097,12 +1110,14 @@ async function fetchCommandCodeJson(url: string, bearer: string): Promise<Record
|
|
|
1097
1110
|
* pools' billing cycle, and `currentPeriodEnd` becomes expiresAt.
|
|
1098
1111
|
*/
|
|
1099
1112
|
async function fetchCommandCodeSpend(
|
|
1113
|
+
provider: string,
|
|
1114
|
+
config: OcxProviderConfig,
|
|
1100
1115
|
bearer: string,
|
|
1101
1116
|
credits: Record<string, unknown> | null,
|
|
1102
1117
|
orgQuery: string,
|
|
1103
1118
|
): Promise<ProviderQuotaCreditsUsd | undefined> {
|
|
1104
1119
|
if (!credits) return undefined;
|
|
1105
|
-
const subscriptionBody = await fetchCommandCodeJson(`${COMMAND_CODE_SUBSCRIPTIONS_URL}${orgQuery}`, bearer);
|
|
1120
|
+
const subscriptionBody = await fetchCommandCodeJson(provider, config, `${COMMAND_CODE_SUBSCRIPTIONS_URL}${orgQuery}`, bearer);
|
|
1106
1121
|
const subscription = asRecord(subscriptionBody?.data) ?? subscriptionBody;
|
|
1107
1122
|
const periodStart = typeof subscription?.currentPeriodStart === "string" ? subscription.currentPeriodStart.trim() : "";
|
|
1108
1123
|
// Unscoped /usage/summary is lifetime spend; mixing it with current-cycle
|
|
@@ -1110,7 +1125,7 @@ async function fetchCommandCodeSpend(
|
|
|
1110
1125
|
if (!periodStart) return undefined;
|
|
1111
1126
|
const sinceQuery = `${orgQuery ? "&" : "?"}since=${encodeURIComponent(periodStart)}`;
|
|
1112
1127
|
const expiresAt = normalizeResetAt(subscription?.currentPeriodEnd);
|
|
1113
|
-
const summaryBody = await fetchCommandCodeJson(`${COMMAND_CODE_USAGE_URL}${orgQuery}${sinceQuery}`, bearer);
|
|
1128
|
+
const summaryBody = await fetchCommandCodeJson(provider, config, `${COMMAND_CODE_USAGE_URL}${orgQuery}${sinceQuery}`, bearer);
|
|
1114
1129
|
const summary = asRecord(summaryBody?.data) ?? summaryBody;
|
|
1115
1130
|
const used = toFiniteNumber(summary?.totalCost) ?? toFiniteNumber(summary?.totalMonthlyCredits);
|
|
1116
1131
|
if (used === undefined || used < 0) return undefined;
|
|
@@ -1161,12 +1176,12 @@ export async function fetchCommandCodeQuota(provider: string, config: OcxProvide
|
|
|
1161
1176
|
// Never release credentials to a user-edited or lookalike provider host.
|
|
1162
1177
|
if (!isCanonicalCommandCodeBaseUrl(config.baseUrl)) return null;
|
|
1163
1178
|
if (!bearer) return null;
|
|
1164
|
-
const whoamiBody = await fetchCommandCodeJson(COMMAND_CODE_WHOAMI_URL, bearer);
|
|
1179
|
+
const whoamiBody = await fetchCommandCodeJson(provider, config, COMMAND_CODE_WHOAMI_URL, bearer);
|
|
1165
1180
|
const whoami = asRecord(whoamiBody?.data) ?? whoamiBody;
|
|
1166
1181
|
const org = asRecord(whoami?.org);
|
|
1167
1182
|
const orgId = typeof org?.id === "string" && org.id.trim() ? org.id.trim() : null;
|
|
1168
1183
|
const orgQuery = orgId ? `?orgId=${encodeURIComponent(orgId)}` : "";
|
|
1169
|
-
const response = await
|
|
1184
|
+
const response = await quotaFetch(provider, config, `${COMMAND_CODE_CREDITS_URL}${orgQuery}`, {
|
|
1170
1185
|
headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` },
|
|
1171
1186
|
redirect: "error",
|
|
1172
1187
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -1183,7 +1198,7 @@ export async function fetchCommandCodeQuota(provider: string, config: OcxProvide
|
|
|
1183
1198
|
if (!credits && !limits) return null;
|
|
1184
1199
|
const fiveHour = parseCommandCodeWindow(limits?.fiveHour);
|
|
1185
1200
|
const weekly = parseCommandCodeWindow(limits?.weekly);
|
|
1186
|
-
const creditsUsd = await fetchCommandCodeSpend(bearer, credits, orgQuery);
|
|
1201
|
+
const creditsUsd = await fetchCommandCodeSpend(provider, config, bearer, credits, orgQuery);
|
|
1187
1202
|
const quota: ProviderQuota = {
|
|
1188
1203
|
...(fiveHour ? {
|
|
1189
1204
|
fiveHourPercent: fiveHour.percent,
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* entitlement gap (muse-spark max needs an active Muse Code subscription) costs one rejected
|
|
19
19
|
* request instead of failing every turn that selects that rung.
|
|
20
20
|
*/
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
21
22
|
import { existsSync, readFileSync } from "node:fs";
|
|
22
23
|
import { join } from "node:path";
|
|
23
24
|
// Leaf modules on purpose: this file is imported from reasoning-effort.ts, which combos/types.ts
|
|
@@ -26,6 +27,7 @@ import { join } from "node:path";
|
|
|
26
27
|
import { atomicWriteFile } from "../config/atomic-write";
|
|
27
28
|
import { getConfigDir } from "../config/paths";
|
|
28
29
|
import type { OcxProviderConfig } from "../types";
|
|
30
|
+
import { resolveProviderApiKey } from "./api-key-resolve";
|
|
29
31
|
|
|
30
32
|
const FILENAME = "reasoning-metadata-cache.json";
|
|
31
33
|
const SUPPORT_FILENAME = "reasoning-support-cache.json";
|
|
@@ -87,7 +89,7 @@ interface MetadataSnapshot {
|
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
interface SupportSnapshot {
|
|
90
|
-
version:
|
|
92
|
+
version: 2;
|
|
91
93
|
rows: Record<string, { effort: string; at: number; evidence?: string }>;
|
|
92
94
|
}
|
|
93
95
|
|
|
@@ -159,11 +161,31 @@ function modelLadderValue(
|
|
|
159
161
|
return undefined;
|
|
160
162
|
}
|
|
161
163
|
|
|
162
|
-
/**
|
|
163
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Bind learned capability to the credential that supplied the evidence. A digest keeps the
|
|
166
|
+
* credential itself out of the persisted cache while remaining stable across restarts and key
|
|
167
|
+
* selection. Providers without key-auth identity may use metadata, but cannot teach the cache.
|
|
168
|
+
*
|
|
169
|
+
* The hash input is the resolved wire credential, not the configured expression: the catalog
|
|
170
|
+
* path carries the raw config string in apiKey (a keychain:/env reference stays unresolved
|
|
171
|
+
* there), while the request path carries the resolved secret in apiKey and the configured
|
|
172
|
+
* expression in _apiKeyAttempt.reference. The request path hashes apiKey exactly as routed --
|
|
173
|
+
* the reference is only provenance, and re-resolving it at record time could read a credential
|
|
174
|
+
* rotated since the request was served. The catalog path resolves the configured expression,
|
|
175
|
+
* so both sides still bind learned refusals to the same wire credential, and a rotation behind
|
|
176
|
+
* a stable reference starts clean instead of inheriting the previous credential's refusals.
|
|
177
|
+
*/
|
|
178
|
+
function credentialIdentity(provider: OcxProviderConfig): string | undefined {
|
|
179
|
+
const resolved = provider._apiKeyAttempt?.reference !== undefined
|
|
180
|
+
? provider.apiKey
|
|
181
|
+
: resolveProviderApiKey(provider.apiKey);
|
|
182
|
+
if (typeof resolved !== "string" || resolved.length === 0) return undefined;
|
|
183
|
+
return createHash("sha256").update(resolved).digest("hex");
|
|
184
|
+
}
|
|
164
185
|
|
|
165
|
-
|
|
166
|
-
|
|
186
|
+
/** JSON encoding avoids delimiter ambiguity in provider, model, and effort identifiers. */
|
|
187
|
+
function supportKey(providerKey: string, credential: string, modelId: string, effort: string): string {
|
|
188
|
+
return JSON.stringify([providerKey, credential, modelId, effort]);
|
|
167
189
|
}
|
|
168
190
|
|
|
169
191
|
function loadSnapshot(): MetadataSnapshot | null {
|
|
@@ -220,7 +242,9 @@ function loadSupport(): Map<string, number> {
|
|
|
220
242
|
}
|
|
221
243
|
const rows = new Map<string, number>();
|
|
222
244
|
const parsed = readJsonFile<SupportSnapshot>(SUPPORT_FILENAME);
|
|
223
|
-
|
|
245
|
+
// Version 1 rows had no credential identity and are deliberately invalidated: accepting them
|
|
246
|
+
// would preserve destination-wide refusals written by a lower-entitlement account.
|
|
247
|
+
if (parsed && parsed.version === 2 && parsed.rows && typeof parsed.rows === "object") {
|
|
224
248
|
for (const [key, row] of Object.entries(parsed.rows)) {
|
|
225
249
|
if (!row || typeof row.at !== "number") continue;
|
|
226
250
|
if (nowMs - row.at > SUPPORT_TTL_MS) continue;
|
|
@@ -273,8 +297,9 @@ export function metadataDeclaresType(provider: OcxProviderConfig, modelId: strin
|
|
|
273
297
|
|
|
274
298
|
export function isReasoningEffortLearnedUnsupported(provider: OcxProviderConfig, modelId: string, effort: string): boolean {
|
|
275
299
|
const key = metadataProviderKey(provider);
|
|
276
|
-
|
|
277
|
-
|
|
300
|
+
const credential = credentialIdentity(provider);
|
|
301
|
+
if (!key || !credential) return false;
|
|
302
|
+
return loadSupport().has(supportKey(key, credential, modelId, effort));
|
|
278
303
|
}
|
|
279
304
|
|
|
280
305
|
/**
|
|
@@ -291,10 +316,11 @@ export function dropLearnedUnsupportedReasoningEfforts(
|
|
|
291
316
|
): string[] {
|
|
292
317
|
if (efforts.length === 0) return [...efforts];
|
|
293
318
|
const key = metadataProviderKey(provider);
|
|
294
|
-
|
|
319
|
+
const credential = credentialIdentity(provider);
|
|
320
|
+
if (!key || !credential) return [...efforts];
|
|
295
321
|
const support = loadSupport();
|
|
296
322
|
if (support.size === 0) return [...efforts];
|
|
297
|
-
const kept = efforts.filter(effort => !support.has(supportKey(key, modelId, effort)));
|
|
323
|
+
const kept = efforts.filter(effort => !support.has(supportKey(key, credential, modelId, effort)));
|
|
298
324
|
return kept.length === 0 ? [...efforts] : kept;
|
|
299
325
|
}
|
|
300
326
|
|
|
@@ -334,8 +360,9 @@ export function recordUnsupportedReasoningEffort(
|
|
|
334
360
|
evidence?: string,
|
|
335
361
|
): boolean {
|
|
336
362
|
const key = metadataProviderKey(provider);
|
|
337
|
-
|
|
338
|
-
|
|
363
|
+
const credential = credentialIdentity(provider);
|
|
364
|
+
if (!key || !credential || !effort) return false;
|
|
365
|
+
const rowKey = supportKey(key, credential, modelId, effort);
|
|
339
366
|
const rows = loadSupport();
|
|
340
367
|
if (rows.has(rowKey)) return false;
|
|
341
368
|
rows.set(rowKey, Date.now());
|
|
@@ -346,15 +373,14 @@ export function recordUnsupportedReasoningEffort(
|
|
|
346
373
|
try {
|
|
347
374
|
const out: SupportSnapshot["rows"] = {};
|
|
348
375
|
for (const [rowKey, at] of rows) {
|
|
349
|
-
const parts = rowKey.split(KEY_SEP);
|
|
350
376
|
const evidenceText = supportEvidence.get(rowKey);
|
|
351
377
|
out[rowKey] = {
|
|
352
|
-
effort:
|
|
378
|
+
effort: JSON.parse(rowKey)[3] ?? "",
|
|
353
379
|
at,
|
|
354
380
|
...(evidenceText ? { evidence: evidenceText } : {}),
|
|
355
381
|
};
|
|
356
382
|
}
|
|
357
|
-
atomicWriteFile(join(getConfigDir(), SUPPORT_FILENAME), JSON.stringify({ version:
|
|
383
|
+
atomicWriteFile(join(getConfigDir(), SUPPORT_FILENAME), JSON.stringify({ version: 2, rows: out }) + "\n");
|
|
358
384
|
} catch {
|
|
359
385
|
// Best-effort persistence only.
|
|
360
386
|
}
|
|
@@ -371,11 +397,10 @@ export function flushReasoningSupportCache(): void {
|
|
|
371
397
|
const rows = loadSupport();
|
|
372
398
|
const out: SupportSnapshot["rows"] = {};
|
|
373
399
|
for (const [rowKey, at] of rows) {
|
|
374
|
-
const parts = rowKey.split(KEY_SEP);
|
|
375
400
|
const evidenceText = supportEvidence.get(rowKey);
|
|
376
|
-
out[rowKey] = { effort:
|
|
401
|
+
out[rowKey] = { effort: JSON.parse(rowKey)[3] ?? "", at, ...(evidenceText ? { evidence: evidenceText } : {}) };
|
|
377
402
|
}
|
|
378
|
-
atomicWriteFile(join(getConfigDir(), SUPPORT_FILENAME), JSON.stringify({ version:
|
|
403
|
+
atomicWriteFile(join(getConfigDir(), SUPPORT_FILENAME), JSON.stringify({ version: 2, rows: out }) + "\n");
|
|
379
404
|
} catch {
|
|
380
405
|
// Best-effort persistence only.
|
|
381
406
|
}
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
deepseekThinkingEffortsFor,
|
|
56
56
|
deepseekReasoningMapFor,
|
|
57
57
|
KIMI_K3_STANDARD_CONTEXT_WINDOW,
|
|
58
|
-
|
|
58
|
+
KIMI_CODING_LIVE_MODELS,
|
|
59
59
|
KIMI_THINKING_MODELS,
|
|
60
60
|
KIMI_CODING_NO_REASONING_MODELS,
|
|
61
61
|
KIMI_CODING_K3_REASONING_EFFORTS,
|
|
@@ -444,8 +444,14 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
444
444
|
oauthId: "kimi",
|
|
445
445
|
jawcodeBundle: "moonshot",
|
|
446
446
|
note: "Log in with your Kimi account",
|
|
447
|
-
|
|
448
|
-
|
|
447
|
+
// 260921: the retired k2.x ids stay out of the picker — live /coding/v1/models lists
|
|
448
|
+
// only kimi-for-coding[-highspeed], k3, k3-256k. Saved rows still naming kimi-k2.7-code
|
|
449
|
+
// are repaired by MODEL_RENAMES in model-rename-migration.ts.
|
|
450
|
+
models: KIMI_CODING_LIVE_MODELS,
|
|
451
|
+
// 260921: kimi-k2.7-code was retired from the subscription endpoint (live /models lists
|
|
452
|
+
// only kimi-for-coding[-highspeed], k3, k3-256k). The kimi-for-coding alias is the
|
|
453
|
+
// stable ID and currently routes to K2.8 Preview.
|
|
454
|
+
defaultModel: "kimi-for-coding",
|
|
449
455
|
modelContextWindows: KIMI_CODING_MODEL_CONTEXT_WINDOWS,
|
|
450
456
|
modelInputModalities: KIMI_CODING_MODEL_INPUT_MODALITIES,
|
|
451
457
|
// K3 accepts low/high/max; Codex aliases are normalized by the model-scoped wire map.
|
|
@@ -1258,4 +1264,3 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
1258
1264
|
note: "Serverless Inference subscription API. Live discovery exposes only kimi-k2-instruct because Vultr documents it as the sole tool-calling model.",
|
|
1259
1265
|
},
|
|
1260
1266
|
];
|
|
1261
|
-
|