@bitkyc08/opencodex 2.75.0 → 2.76.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/README.md +6 -1
- package/bin/ocx.mjs +12 -1
- package/gui/dist/assets/App-CddorHdA.js +51 -0
- package/gui/dist/assets/App-DMudtISW.css +1 -0
- package/gui/dist/assets/{Tray-CGAD2AGk.js → Tray-DjNwwrWS.js} +1 -1
- package/gui/dist/assets/index-Btze1I3T.css +1 -0
- package/gui/dist/assets/index-CvnY7E6i.js +86 -0
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/opengateway.svg +12 -0
- package/package.json +1 -1
- package/src/adapters/anthropic/account-metadata.ts +17 -0
- package/src/adapters/anthropic/client-identity.ts +69 -0
- package/src/adapters/anthropic/passthrough.ts +23 -4
- package/src/adapters/anthropic.ts +18 -1
- package/src/adapters/command-code-project-context.ts +7 -11
- package/src/adapters/devin/cloud-direct/tool-schema.ts +12 -48
- package/src/adapters/kiro-retry.ts +23 -8
- package/src/adapters/openai-chat/messages.ts +1 -1
- package/src/adapters/registry.ts +8 -1
- package/src/adapters/zed.ts +377 -0
- package/src/bridge/sse.ts +4 -2
- package/src/chatgpt/app-server-shim/app-server-rewrite.ts +45 -0
- package/src/chatgpt/app-server-shim/bundle-trust.ts +77 -0
- package/src/chatgpt/app-server-shim/filter.ts +157 -0
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +99 -0
- package/src/chatgpt/app-server-shim/launcher.ts +91 -0
- package/src/claude/desktop-3p-startup.ts +62 -0
- package/src/claude/desktop-3p.ts +5 -0
- package/src/claude/first-party-settings.ts +14 -0
- package/src/claude/intercept/cli-catalog.ts +150 -0
- package/src/claude/intercept/cli-picker.ts +82 -0
- package/src/claude/intercept/listener.ts +21 -6
- package/src/claude/intercept/picker-bootstrap.ts +23 -5
- package/src/claude/intercept/picker-models.ts +57 -13
- package/src/claude/intercept/runtime.ts +30 -2
- package/src/claude/intercept/settings.ts +3 -0
- package/src/cli/agent.ts +172 -0
- package/src/cli/alias.ts +2 -1
- package/src/cli/capabilities-command.ts +3 -2
- package/src/cli/capabilities.ts +87 -0
- package/src/cli/chatgpt-command.ts +140 -0
- package/src/cli/combo.ts +144 -19
- package/src/cli/config-command.ts +3 -1
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +5 -24
- package/src/cli/init.ts +16 -2
- package/src/cli/integrations.ts +13 -0
- package/src/cli/internal-command.ts +12 -5
- package/src/cli/observe.ts +6 -1
- package/src/cli/registry.ts +6 -0
- package/src/cli/route-policy.ts +3 -1
- package/src/cli/runtime-api.ts +5 -2
- package/src/cli/status-oauth.ts +5 -0
- package/src/cli/status.ts +8 -1
- package/src/cli/stop-approval.ts +55 -16
- package/src/cli/stop-report.ts +5 -0
- package/src/clients/lazycodex.ts +48 -0
- package/src/clients/omo-role-models.ts +111 -0
- package/src/codex/account-credit-use.ts +99 -0
- package/src/codex/account-lifecycle.ts +2 -0
- package/src/codex/agent-role-models.ts +171 -0
- package/src/codex/auth-api/account-list.ts +10 -3
- package/src/codex/auth-api/routes.ts +34 -0
- package/src/codex/auth-context.ts +52 -11
- package/src/codex/catalog/discovered-natives.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +6 -1
- package/src/codex/catalog/model-hints.ts +9 -1
- package/src/codex/catalog/provider-models.ts +60 -0
- package/src/codex/catalog/routed-gather.ts +30 -5
- package/src/codex/convergence-types.ts +2 -1
- package/src/codex/main-account-external-usage.ts +62 -0
- package/src/codex/main-account-hard-lock.ts +24 -13
- package/src/codex/native-profile-processes.ts +53 -16
- package/src/codex/prompt-layers/toml-edit.ts +1 -1
- package/src/codex/quota-types.ts +3 -7
- package/src/codex/quota.ts +19 -4
- package/src/codex/role-auto-assign.ts +159 -0
- package/src/codex/role-sizing-limits.ts +2 -0
- package/src/codex/role-sizing.ts +216 -0
- package/src/codex/routing/selection.ts +36 -0
- package/src/codex/routing/thread-affinity.ts +2 -0
- package/src/codex/routing.ts +4 -4
- package/src/codex/subagent-model-fallback.ts +82 -26
- package/src/codex/subagent-selectable-models.ts +32 -0
- package/src/combos/index.ts +18 -0
- package/src/combos/jev-decision-contract.ts +20 -0
- package/src/combos/jev-dispatch.ts +18 -0
- package/src/combos/jev-model-backend.ts +127 -0
- package/src/combos/jev.ts +249 -33
- package/src/combos/types.ts +125 -2
- package/src/config/diagnostics.ts +18 -0
- package/src/config/macos-system-proxy.ts +72 -15
- package/src/config/owner-registry.ts +13 -4
- package/src/config/process-state.ts +17 -8
- package/src/config/proxy-env.ts +35 -5
- package/src/config/schema/config-schema.ts +30 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config/serving-runtimes.ts +95 -1
- package/src/generated/compatibility-version.json +336 -156
- package/src/lib/errors.ts +7 -7
- package/src/lib/jsonc.ts +13 -0
- package/src/lib/local-chat-completion.ts +144 -0
- package/src/lib/provider-outbound.ts +87 -1
- package/src/lib/provider-runtime-fetch.ts +23 -0
- package/src/lib/provider-tls-profile.ts +221 -0
- package/src/lib/request-execution-budget.ts +16 -6
- package/src/lib/spend-reservation-ledger.ts +54 -20
- package/src/lib/sse-decoder.ts +20 -17
- package/src/lib/synced-state-location.ts +96 -0
- package/src/lib/winsw.ts +14 -5
- package/src/oauth/anthropic-account-refusal.ts +53 -4
- package/src/oauth/anthropic-model-quota.ts +83 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +37 -0
- package/src/oauth/anthropic-routing.ts +112 -58
- package/src/oauth/health.ts +39 -3
- package/src/oauth/index.ts +14 -0
- package/src/oauth/kiro-account-load.ts +10 -0
- package/src/oauth/zed.ts +134 -0
- package/src/providers/model-discovery.ts +19 -8
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/provider-id-rewrite.ts +9 -2
- package/src/providers/quota/account-cache.ts +29 -7
- package/src/providers/quota/anthropic-cooldown-recovery.ts +18 -0
- package/src/providers/quota/anthropic-family-headers.ts +25 -0
- package/src/providers/quota/vendor-probes-oauth.ts +5 -1
- package/src/providers/quota-types.ts +4 -0
- package/src/providers/registry/entries-core.ts +18 -0
- package/src/providers/registry/entries-extended.ts +55 -0
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/xai-fast-model.ts +12 -4
- package/src/providers/zed.ts +598 -0
- package/src/responses/state/snapshot-select.ts +22 -9
- package/src/responses/state.ts +2 -3
- package/src/router.ts +9 -0
- package/src/server/anthropic-rate-limit-headers.ts +7 -0
- package/src/server/auth-cors.ts +7 -0
- package/src/server/chat-completions.ts +9 -4
- package/src/server/claude-messages.ts +59 -21
- package/src/server/index/claude-intercept-lifecycle.ts +71 -25
- package/src/server/index/optional-listeners.ts +4 -0
- package/src/server/index/spend-ledger-lifecycle.ts +4 -0
- package/src/server/management/agent-settings-routes.ts +74 -37
- package/src/server/management/claude-desktop-picker-routes.ts +7 -0
- package/src/server/management/claude-intercept-routes.ts +35 -0
- package/src/server/management/codex-agent-role-routes.ts +122 -0
- package/src/server/management/codex-role-auto-assign.ts +208 -0
- package/src/server/management/combo-routes.ts +62 -5
- package/src/server/management/config-routes.ts +35 -3
- package/src/server/management/context.ts +4 -1
- package/src/server/management/decision-discovery.ts +74 -0
- package/src/server/management/decision-model-validation.ts +80 -0
- package/src/server/management/decision-routes.ts +160 -0
- package/src/server/management/native-integration-routes.ts +16 -0
- package/src/server/management/provider-routes.ts +30 -34
- package/src/server/management/route-registry.ts +12 -0
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management/zed-provider-probe.ts +38 -0
- package/src/server/management-api.ts +12 -1
- package/src/server/messages-native-oauth.ts +7 -2
- package/src/server/messages-native.ts +7 -3
- package/src/server/relay.ts +3 -1
- package/src/server/request-log.ts +2 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +18 -3
- package/src/server/responses/compact.ts +14 -1
- package/src/server/responses/core-auth.ts +2 -0
- package/src/server/responses/core-combo.ts +67 -11
- package/src/server/responses/core-normalize.ts +21 -1
- package/src/server/responses/core-options.ts +11 -1
- package/src/server/responses/core.ts +5 -3
- package/src/server/responses/fetch-helpers.ts +6 -3
- package/src/server/responses/jev-model-invoke.ts +190 -0
- package/src/server/responses/passthrough-delivery.ts +16 -0
- package/src/server/responses/policy-fallback.ts +55 -3
- package/src/server/responses/policy-request-scope.ts +80 -0
- package/src/server/responses/request-prepare.ts +70 -18
- package/src/server/responses/request-transport.ts +54 -23
- package/src/server/responses/run-turn-execution.ts +1 -0
- package/src/server/responses/sidecar-execution.ts +9 -3
- package/src/server/responses-hosted-image-display.ts +225 -0
- package/src/service/managing-cli.ts +34 -4
- package/src/service/systemd-env.ts +75 -0
- package/src/service/windows-guarded-manager.ts +60 -19
- package/src/service/windows-scheduler.ts +22 -3
- package/src/service/windows-taskxml.ts +32 -10
- package/src/service-manager-probe.ts +72 -19
- package/src/types/config.ts +31 -1
- package/src/types/provider.ts +2 -0
- package/src/types/request.ts +2 -0
- package/src/update/index.ts +39 -6
- package/src/update/job.ts +63 -63
- package/src/update/npm-cache-preflight.d.mts +33 -1
- package/src/update/npm-cache-preflight.mjs +131 -18
- package/src/update/ownership-transaction.ts +19 -1
- package/src/update/restart-ownership.ts +80 -6
- package/src/update/transactional-install.d.mts +2 -0
- package/src/update/transactional-install.mjs +6 -1
- package/src/usage/jev-stats.ts +28 -1
- package/src/usage/log.ts +1 -1
- package/src/vision/anthropic-describe.ts +5 -3
- package/src/vision/index.ts +1 -0
- package/src/vision/plan.ts +2 -1
- package/src/vision/routed-describe.ts +29 -121
- package/src/web-search/alpha-search.ts +3 -3
- package/src/web-search/anthropic-executor.ts +6 -4
- package/src/web-search/index.ts +1 -1
- package/src/web-search/loop.ts +3 -3
- package/src/web-search/passthrough-bridge.ts +2 -1
- package/src/web-search/run-turn-loop.ts +1 -1
- package/src/web-search/sidecar-providers.ts +2 -1
- package/gui/dist/assets/App-ia4U0ufY.css +0 -1
- package/gui/dist/assets/App-xm0zGNOe.js +0 -51
- package/gui/dist/assets/index-DBT3Q31Y.css +0 -1
- package/gui/dist/assets/index-D_RGAT6Q.js +0 -86
- package/gui/dist/assets/quota-summary-CUFq48t-.js +0 -1
package/src/oauth/zed.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { OAuthController, OAuthCredentials } from "./types";
|
|
2
|
+
import {
|
|
3
|
+
createZedNativeAuthData,
|
|
4
|
+
decryptZedAccessToken,
|
|
5
|
+
parseZedCallbackPayload,
|
|
6
|
+
} from "../providers/zed";
|
|
7
|
+
|
|
8
|
+
const LOGIN_TIMEOUT_MS = 300_000;
|
|
9
|
+
|
|
10
|
+
type ZedServer = ReturnType<typeof Bun.serve>;
|
|
11
|
+
|
|
12
|
+
function closingResponse(body: string, status: number): Response {
|
|
13
|
+
return new Response(body, {
|
|
14
|
+
status,
|
|
15
|
+
headers: { "Content-Type": "text/html; charset=utf-8", Connection: "close" },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function callbackSuccess(): Response {
|
|
20
|
+
return closingResponse(
|
|
21
|
+
"<!doctype html><html><body><h2>Login complete</h2><p>You can close this tab.</p></body></html>",
|
|
22
|
+
200,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function callbackFailure(message: string): Response {
|
|
27
|
+
// Do not reflect the callback query, encrypted token, or decryption error details into HTML.
|
|
28
|
+
void message;
|
|
29
|
+
return closingResponse(
|
|
30
|
+
"<!doctype html><html><body><h2>Login failed</h2><p>Return to OpenCodex and retry the login.</p></body></html>",
|
|
31
|
+
400,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function parseManualCallback(input: string, privateKeyVerifier: string): Promise<OAuthCredentials> {
|
|
36
|
+
const payload = parseZedCallbackPayload(input);
|
|
37
|
+
const access = decryptZedAccessToken(payload.encryptedAccessToken, privateKeyVerifier);
|
|
38
|
+
return {
|
|
39
|
+
access,
|
|
40
|
+
refresh: access,
|
|
41
|
+
expires: Number.MAX_SAFE_INTEGER,
|
|
42
|
+
accountId: payload.userId,
|
|
43
|
+
source: "oauth",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Zed native-app login: local RSA callback, not OAuth2/PKCE and not refreshable. */
|
|
48
|
+
export async function loginZed(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
49
|
+
let resolveCallback: ((credential: OAuthCredentials) => void) | undefined;
|
|
50
|
+
const callback = new Promise<OAuthCredentials>(resolve => {
|
|
51
|
+
resolveCallback = resolve;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
let privateKeyVerifier: string | undefined;
|
|
55
|
+
let server: ZedServer | undefined;
|
|
56
|
+
// Ends the manual-paste branch once the race settles, so a late paste can neither re-prompt
|
|
57
|
+
// nor keep the private key alive after the callback (or cancellation) already won.
|
|
58
|
+
const manualStop = new AbortController();
|
|
59
|
+
const manualStopped = new Promise<undefined>(resolve => {
|
|
60
|
+
manualStop.signal.addEventListener("abort", () => resolve(undefined), { once: true });
|
|
61
|
+
});
|
|
62
|
+
try {
|
|
63
|
+
server = Bun.serve({
|
|
64
|
+
hostname: "127.0.0.1",
|
|
65
|
+
port: 0,
|
|
66
|
+
fetch: async request => {
|
|
67
|
+
const url = new URL(request.url);
|
|
68
|
+
if (request.method !== "GET" || url.pathname !== "/") return closingResponse("Not found", 404);
|
|
69
|
+
try {
|
|
70
|
+
if (!privateKeyVerifier) throw new Error("Zed login callback is not initialized");
|
|
71
|
+
const credential = await parseManualCallback(url.toString(), privateKeyVerifier);
|
|
72
|
+
resolveCallback?.(credential);
|
|
73
|
+
return callbackSuccess();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return callbackFailure(error instanceof Error ? error.message : "invalid callback");
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
if (!server.port) throw new Error("Zed login callback port was not allocated");
|
|
80
|
+
const authData = createZedNativeAuthData(server.port);
|
|
81
|
+
privateKeyVerifier = authData.privateKeyVerifier;
|
|
82
|
+
ctrl.onAuth?.({
|
|
83
|
+
url: authData.authUrl,
|
|
84
|
+
instructions: "Sign in with your Zed account in the browser, then return here after Zed redirects to the local callback.",
|
|
85
|
+
});
|
|
86
|
+
ctrl.onProgress?.("Waiting for Zed login callback…");
|
|
87
|
+
|
|
88
|
+
const timeout = AbortSignal.timeout(LOGIN_TIMEOUT_MS);
|
|
89
|
+
const signal = ctrl.signal ? AbortSignal.any([ctrl.signal, timeout]) : timeout;
|
|
90
|
+
const cancelled = new Promise<never>((_, reject) => {
|
|
91
|
+
if (signal.aborted) {
|
|
92
|
+
reject(new Error("Zed login cancelled"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
signal.addEventListener("abort", () => reject(new Error("Zed login cancelled")), { once: true });
|
|
96
|
+
});
|
|
97
|
+
const manual = ctrl.onManualCodeInput
|
|
98
|
+
? (async (): Promise<OAuthCredentials> => {
|
|
99
|
+
let verifier: string | undefined = authData.privateKeyVerifier;
|
|
100
|
+
while (!manualStop.signal.aborted && !signal.aborted) {
|
|
101
|
+
const input = await Promise.race([ctrl.onManualCodeInput?.(), manualStopped]);
|
|
102
|
+
if (manualStop.signal.aborted || signal.aborted) break;
|
|
103
|
+
if (!input) {
|
|
104
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
try { return await parseManualCallback(input, verifier); } catch { /* keep waiting */ }
|
|
108
|
+
}
|
|
109
|
+
verifier = undefined;
|
|
110
|
+
// Never settles: the race already has its answer, and this branch must not override it.
|
|
111
|
+
return new Promise<never>(() => {});
|
|
112
|
+
})()
|
|
113
|
+
: undefined;
|
|
114
|
+
return await Promise.race([callback, cancelled, ...(manual ? [manual] : [])]);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw error;
|
|
117
|
+
} finally {
|
|
118
|
+
manualStop.abort();
|
|
119
|
+
server?.stop(true);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Zed's long-lived native token has no refresh endpoint. */
|
|
124
|
+
export async function refreshZedToken(refresh: string, _signal?: AbortSignal, credential?: OAuthCredentials): Promise<OAuthCredentials> {
|
|
125
|
+
const accountId = credential?.accountId;
|
|
126
|
+
if (!accountId || !refresh) throw new Error("Zed credential is missing its user id");
|
|
127
|
+
return {
|
|
128
|
+
access: refresh,
|
|
129
|
+
refresh,
|
|
130
|
+
expires: Number.MAX_SAFE_INTEGER,
|
|
131
|
+
accountId,
|
|
132
|
+
source: credential?.source ?? "oauth",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -78,6 +78,8 @@ function discoveryPredicateError(predicate: ProviderModelDiscoveryPredicate): st
|
|
|
78
78
|
if (predicate.path.some(segment => typeof segment !== "string" || !segment.trim() || segment.length > 64)) {
|
|
79
79
|
return "predicate path segments must be nonblank strings up to 64 characters";
|
|
80
80
|
}
|
|
81
|
+
// One projection is sufficient for providers[].id and avoids combinatorial traversal.
|
|
82
|
+
if (predicate.path.filter(segment => segment === "*").length > 1) return "predicate path may contain at most one array projection";
|
|
81
83
|
const values = "equalsAny" in predicate
|
|
82
84
|
? predicate.equalsAny
|
|
83
85
|
: "containsAny" in predicate
|
|
@@ -145,7 +147,7 @@ export function providerModelDiscoverySpecError(spec: ProviderModelDiscoverySpec
|
|
|
145
147
|
return `${field} must be a positive integer no greater than ${hardLimit}`;
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
|
-
for (const [group, predicates] of Object.entries(spec.filter
|
|
150
|
+
for (const [group, predicates] of Object.entries({ ...spec.filter, ...(spec.preferFirst === undefined ? {} : { preferFirst: spec.preferFirst }) })) {
|
|
149
151
|
if (!Array.isArray(predicates) || predicates.length === 0 || predicates.length > 32) {
|
|
150
152
|
return `${group} must contain 1-32 predicates`;
|
|
151
153
|
}
|
|
@@ -349,12 +351,17 @@ export async function readBoundedDiscoveryJson(
|
|
|
349
351
|
}
|
|
350
352
|
|
|
351
353
|
function valueAtPath(item: Record<string, unknown>, path: readonly string[]): unknown {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
+
const visit = (current: unknown, index: number): unknown => {
|
|
355
|
+
if (index === path.length) return current;
|
|
356
|
+
const segment = path[index];
|
|
357
|
+
if (segment === "*") {
|
|
358
|
+
if (!Array.isArray(current)) return undefined;
|
|
359
|
+
return current.slice(0, MODEL_DISCOVERY_MAX_FILTER_VALUES).map(value => visit(value, index + 1));
|
|
360
|
+
}
|
|
354
361
|
if (current === null || typeof current !== "object" || Array.isArray(current)) return undefined;
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
return
|
|
362
|
+
return visit((current as Record<string, unknown>)[segment!], index + 1);
|
|
363
|
+
};
|
|
364
|
+
return visit(item, 0);
|
|
358
365
|
}
|
|
359
366
|
|
|
360
367
|
function comparableScalar(value: unknown, caseInsensitive: boolean): ProviderModelDiscoveryScalar | undefined {
|
|
@@ -522,6 +529,7 @@ export function extractProviderModelItems(
|
|
|
522
529
|
}
|
|
523
530
|
|
|
524
531
|
const items: ProviderModelsApiItem[] = [];
|
|
532
|
+
const preferred: ProviderModelsApiItem[] = [];
|
|
525
533
|
const seen = new Set<string>();
|
|
526
534
|
const idField = discovery.spec?.idField ?? "id";
|
|
527
535
|
for (const raw of data) {
|
|
@@ -547,7 +555,10 @@ export function extractProviderModelItems(
|
|
|
547
555
|
// are published, only what is known about an already-admitted one.
|
|
548
556
|
if (!providerModelMatchesDiscoveryFilter(item, discovery.spec?.filter) || seen.has(finalId)) continue;
|
|
549
557
|
seen.add(finalId);
|
|
550
|
-
|
|
558
|
+
// Preference, like admission, must not be supplied by sibling enrichment.
|
|
559
|
+
const target = discovery.spec?.preferFirst?.some(predicate => predicateMatches(raw as ProviderModelsApiItem, predicate))
|
|
560
|
+
? preferred : items;
|
|
561
|
+
target.push(siblings ? enrichAdmittedModel(item, siblings) : item);
|
|
551
562
|
}
|
|
552
|
-
return { ok: true, items, rawCount: data.length };
|
|
563
|
+
return { ok: true, items: [...preferred, ...items], rawCount: data.length };
|
|
553
564
|
}
|
|
@@ -142,6 +142,7 @@ export const PROVIDER_MODEL_RENAME_ROLES = {
|
|
|
142
142
|
desktopExecutor: "none",
|
|
143
143
|
unsafeAllowNativeLocalExec: "none",
|
|
144
144
|
nativeLocalExec: "none",
|
|
145
|
+
tlsProfile: "none",
|
|
145
146
|
} as const satisfies Record<keyof OcxProviderConfig, ModelRenameRole>;
|
|
146
147
|
|
|
147
148
|
function fieldsWithRole(role: ModelRenameRole): string[] {
|
|
@@ -19,8 +19,8 @@ export interface ProviderRewriteResult {
|
|
|
19
19
|
* Re-point every config reference from one provider id to another.
|
|
20
20
|
*
|
|
21
21
|
* Three shapes exist and the difference matters: routed model strings
|
|
22
|
-
* (`"<provider>/<model>"`), bare provider ids (`customModels[].provider`,
|
|
23
|
-
* `combos[*].targets[].provider`, `routingProfiles[*].candidates[].provider`),
|
|
22
|
+
* (`"<provider>/<model>"`, including `combos[*].decisionModel`), bare provider ids (`customModels[].provider`,
|
|
23
|
+
* `combos[*].targets[].provider`, `combos[*].decisionProvider`, `routingProfiles[*].candidates[].provider`),
|
|
24
24
|
* and keys that ARE provider ids or routes (`providerContextCaps`,
|
|
25
25
|
* `claudeCode.desktopProfile.assignments`). A rewrite that handles only the
|
|
26
26
|
* first leaves an orphaned context cap and — worse — a combo target or routing
|
|
@@ -106,12 +106,19 @@ export function rewriteProviderReferences(config: OcxConfig, from: string, to: s
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
for (const combo of Object.values(config.combos ?? {})) {
|
|
109
|
+
const decisionModel = route(typeof combo.decisionModel === "string" ? combo.decisionModel.trim() : combo.decisionModel);
|
|
110
|
+
if (decisionModel) combo.decisionModel = decisionModel;
|
|
109
111
|
for (const target of combo.targets ?? []) {
|
|
110
112
|
if (target.provider === from) {
|
|
111
113
|
target.provider = to;
|
|
112
114
|
changed += 1;
|
|
113
115
|
}
|
|
114
116
|
}
|
|
117
|
+
// A JEV Combo's decision service is validated against configured providers too.
|
|
118
|
+
if (typeof combo.decisionProvider === "string" && combo.decisionProvider.trim() === from) {
|
|
119
|
+
combo.decisionProvider = to;
|
|
120
|
+
changed += 1;
|
|
121
|
+
}
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
// Routing-profile candidates carry a bare provider id next to a bare model
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { parseAnthropicFamilyHeaders, mergeAnthropicFamilyWindows } from "./anthropic-family-headers";
|
|
2
|
+
import { observeAnthropicFamilyQuota, clearAnthropicRequestedFamilyQuota, ANTHROPIC_PASSIVE_FAMILY_MAX_AGE_MS } from "../../oauth/anthropic-model-quota";
|
|
1
3
|
import { createHash } from "node:crypto";
|
|
2
4
|
import { getValidAccessTokenForAccount } from "../../oauth";
|
|
3
|
-
import { getAccountCredential, getAccountCredentialWithStatus, getAccountSet } from "../../oauth/store";
|
|
5
|
+
import { credentialGeneration, getAccountCredential, getAccountCredentialWithStatus, getAccountSet } from "../../oauth/store";
|
|
4
6
|
import type { GenerationContext } from "../../lib/state-store-sweeper";
|
|
5
7
|
import { ACCOUNT_QUOTA_TTL_MS, toFiniteNumber } from "../quota-wire";
|
|
6
8
|
import { clearKiroAccountUsageState, hydrateKiroUsageVerdict, kiroPersistableVerdicts, reconcileKiroAccountUsageState } from "../kiro-usage";
|
|
@@ -34,6 +36,8 @@ export type AccountQuotaCacheEntry = {
|
|
|
34
36
|
quotaFailureIsCurrent?: () => boolean;
|
|
35
37
|
/** Private new-reader identity; never persisted or serialized. */
|
|
36
38
|
identity?: string;
|
|
39
|
+
/** Private passive Anthropic ownership fence; not serialized with quota bars. */
|
|
40
|
+
anthropicCredentialGeneration?: string;
|
|
37
41
|
isCurrent?: () => boolean;
|
|
38
42
|
};
|
|
39
43
|
/** Expired measurements become unknown; missing reset evidence never implies a fresh allowance. */
|
|
@@ -67,7 +71,10 @@ export function normalizeAnthropicQuota(quota: ProviderQuota | null | undefined,
|
|
|
67
71
|
changed = true;
|
|
68
72
|
continue;
|
|
69
73
|
}
|
|
70
|
-
if (
|
|
74
|
+
if ((window.passiveObservedAt !== undefined && (typeof window.passiveObservedAt !== "number"
|
|
75
|
+
|| !Number.isFinite(window.passiveObservedAt) || window.passiveObservedAt <= 0 || window.passiveObservedAt > now
|
|
76
|
+
|| now - window.passiveObservedAt >= ANTHROPIC_PASSIVE_FAMILY_MAX_AGE_MS))
|
|
77
|
+
|| validReset(window.resetAt) && window.resetAt <= now) {
|
|
71
78
|
changed = true;
|
|
72
79
|
continue;
|
|
73
80
|
}
|
|
@@ -202,6 +209,10 @@ export function getCachedProviderAccountQuota(provider: string, accountId: strin
|
|
|
202
209
|
const account = getAccountSet("kiro")?.accounts.find(row => row.id === accountId);
|
|
203
210
|
if (!account || entry?.identity !== kiroEvidenceIdentity(account)) return null;
|
|
204
211
|
}
|
|
212
|
+
if (provider === "anthropic" && entry?.anthropicCredentialGeneration) {
|
|
213
|
+
const credential = getAccountCredential(provider, accountId);
|
|
214
|
+
if (!credential || credentialGeneration(credential) !== entry.anthropicCredentialGeneration) return null;
|
|
215
|
+
}
|
|
205
216
|
if (entry?.isCurrent && !entry.isCurrent()) return null;
|
|
206
217
|
return provider === "anthropic" ? normalizeAnthropicQuota(entry?.quota, Date.now()) : entry?.quota ?? null;
|
|
207
218
|
}
|
|
@@ -230,10 +241,11 @@ function anthropicHeaderResetAt(value: string | null): number | undefined {
|
|
|
230
241
|
return Number.isFinite(new Date(timestamp).getTime()) ? timestamp : undefined;
|
|
231
242
|
}
|
|
232
243
|
|
|
233
|
-
export function parseAnthropicRateLimitHeaders(headers: Headers): ProviderQuota | null {
|
|
244
|
+
export function parseAnthropicRateLimitHeaders(headers: Headers, status?: number): ProviderQuota | null {
|
|
234
245
|
const fiveHourPercent = normalizeUtilizationFraction(headers.get("anthropic-ratelimit-unified-5h-utilization"));
|
|
235
246
|
const weeklyPercent = normalizeUtilizationFraction(headers.get("anthropic-ratelimit-unified-7d-utilization"));
|
|
236
|
-
|
|
247
|
+
const customWindows = parseAnthropicFamilyHeaders(headers, Date.now(), status);
|
|
248
|
+
if (fiveHourPercent === undefined && weeklyPercent === undefined && !customWindows.length) return null;
|
|
237
249
|
const fiveHourResetAt = anthropicHeaderResetAt(headers.get("anthropic-ratelimit-unified-5h-reset"));
|
|
238
250
|
const weeklyResetAt = anthropicHeaderResetAt(headers.get("anthropic-ratelimit-unified-7d-reset"));
|
|
239
251
|
return {
|
|
@@ -241,6 +253,7 @@ export function parseAnthropicRateLimitHeaders(headers: Headers): ProviderQuota
|
|
|
241
253
|
...(fiveHourPercent !== undefined && fiveHourResetAt !== undefined ? { fiveHourResetAt } : {}),
|
|
242
254
|
...(weeklyPercent !== undefined ? { weeklyPercent } : {}),
|
|
243
255
|
...(weeklyPercent !== undefined && weeklyResetAt !== undefined ? { weeklyResetAt } : {}),
|
|
256
|
+
...(customWindows.length ? { customWindows } : {}),
|
|
244
257
|
updatedAt: Date.now(),
|
|
245
258
|
};
|
|
246
259
|
}
|
|
@@ -261,25 +274,34 @@ export function recordAnthropicAccountQuotaFromHeaders(
|
|
|
261
274
|
accountId: string,
|
|
262
275
|
headers: Headers,
|
|
263
276
|
writerGeneration: number,
|
|
277
|
+
status?: number,
|
|
278
|
+
model?: string,
|
|
264
279
|
): void {
|
|
265
280
|
if (!accountId) return;
|
|
266
|
-
const observed = parseAnthropicRateLimitHeaders(headers);
|
|
267
|
-
if (!observed) return;
|
|
268
281
|
const key = accountCacheKey("anthropic", accountId);
|
|
269
282
|
if (!mayCommitAccountQuotaKey(key, writerGeneration)) return;
|
|
283
|
+
if (status !== undefined && status >= 200 && status < 300) clearAnthropicRequestedFamilyQuota(accountId, model);
|
|
284
|
+
const observed = parseAnthropicRateLimitHeaders(headers, status);
|
|
285
|
+
if (!observed) return;
|
|
270
286
|
// Hydrate before writing, for the same reason `recordPassiveAccountQuota` does: this write
|
|
271
287
|
// arrives unprompted from the request path, and `persistAccountQuotaCache` serializes the
|
|
272
288
|
// whole map. Landing before any reader has hydrated would persist this single row and erase
|
|
273
289
|
// every other provider's saved row.
|
|
274
290
|
hydrateAccountQuotaCache();
|
|
275
291
|
const candidate = accountQuotaCache.get(key);
|
|
276
|
-
const
|
|
292
|
+
const credential = getAccountCredential("anthropic", accountId);
|
|
293
|
+
const generation = credential && credentialGeneration(credential);
|
|
294
|
+
const previous = candidate?.isCurrent?.() === false
|
|
295
|
+
|| candidate?.anthropicCredentialGeneration && candidate.anthropicCredentialGeneration !== generation ? undefined : candidate;
|
|
296
|
+
if (observed.customWindows) observeAnthropicFamilyQuota(accountId, observed.customWindows, observed.updatedAt);
|
|
277
297
|
accountQuotaCache.set(key, {
|
|
278
298
|
...previous,
|
|
299
|
+
...(generation ? { anthropicCredentialGeneration: generation, isCurrent: undefined } : {}),
|
|
279
300
|
// Headers do not prove that the last usage probe succeeded.
|
|
280
301
|
ts: previous?.ts ?? 0,
|
|
281
302
|
quota: normalizeAnthropicQuota({
|
|
282
303
|
...normalizeAnthropicQuota(previous?.quota, observed.updatedAt), ...observed,
|
|
304
|
+
...(observed.customWindows ? { customWindows: mergeAnthropicFamilyWindows(previous?.quota?.customWindows, observed.customWindows) } : {}),
|
|
283
305
|
}, observed.updatedAt),
|
|
284
306
|
});
|
|
285
307
|
persistAccountQuotaCache();
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { mergeAnthropicFamilyWindows, hasAnthropicFamilyEnumeration } from "./anthropic-family-headers";
|
|
2
|
+
import { getCachedProviderAccountQuota } from "./account-cache";
|
|
3
|
+
import { observeAnthropicFamilyQuota, anthropicFamilyQuotaGeneration } from "../../oauth/anthropic-model-quota";
|
|
1
4
|
import { credentialGeneration, getAccountCredential, getAccountCredentialWithStatus } from "../../oauth/store";
|
|
2
5
|
import type { ProviderQuota } from "../quota-types";
|
|
3
6
|
|
|
@@ -42,6 +45,7 @@ export function clearAnthropicCooldownGenerations(): void {
|
|
|
42
45
|
export type AnthropicCooldownRecoveryProbe = Readonly<{
|
|
43
46
|
requiresFreshDispatch: boolean;
|
|
44
47
|
isCurrentCredential(): boolean;
|
|
48
|
+
isCurrentFamily(): boolean;
|
|
45
49
|
isCurrent(): boolean;
|
|
46
50
|
settle(quota: ProviderQuota): "cleared" | "retained" | "superseded";
|
|
47
51
|
}>;
|
|
@@ -67,6 +71,7 @@ export async function captureAnthropicCooldownRecoveryProbe(
|
|
|
67
71
|
// Lazy because anthropic-routing reads the quota cache on normal request routing.
|
|
68
72
|
const routing = await import("../../oauth/anthropic-routing");
|
|
69
73
|
const cooldownGeneration = anthropicCooldownGeneration(accountId);
|
|
74
|
+
const familyGeneration = anthropicFamilyQuotaGeneration(accountId);
|
|
70
75
|
const claim = routing.captureAnthropicCooldownRecovery(accountId);
|
|
71
76
|
const isCurrentCredential = () => {
|
|
72
77
|
const current = getAccountCredential("anthropic", accountId);
|
|
@@ -75,6 +80,7 @@ export async function captureAnthropicCooldownRecoveryProbe(
|
|
|
75
80
|
return {
|
|
76
81
|
requiresFreshDispatch: claim !== null,
|
|
77
82
|
isCurrentCredential,
|
|
83
|
+
isCurrentFamily: () => anthropicFamilyQuotaGeneration(accountId) === familyGeneration,
|
|
78
84
|
isCurrent: () => isCurrentCredential()
|
|
79
85
|
&& anthropicCooldownGeneration(accountId) === cooldownGeneration,
|
|
80
86
|
settle: quota => claim === null ? "retained"
|
|
@@ -107,12 +113,24 @@ export async function probeAnthropicQuotaWithRecovery(
|
|
|
107
113
|
}
|
|
108
114
|
// Clearing the claimed cooldown intentionally advances the fence. Adopt that exact new
|
|
109
115
|
// generation; any later observation/429 then invalidates publication before a cache write.
|
|
116
|
+
const ownsFamily = probe.isCurrentFamily();
|
|
117
|
+
const authoritative = ownsFamily && hasAnthropicFamilyEnumeration(quota);
|
|
118
|
+
if (ownsFamily) observeAnthropicFamilyQuota(accountId, quota.customWindows ?? [], quota.updatedAt, authoritative);
|
|
119
|
+
if (!authoritative) {
|
|
120
|
+
const cached = getCachedProviderAccountQuota("anthropic", accountId)?.customWindows;
|
|
121
|
+
const windows = ownsFamily ? mergeAnthropicFamilyWindows(cached, quota.customWindows)
|
|
122
|
+
: cached ?? [];
|
|
123
|
+
// A superseded family enumeration has no authority, including over absent families.
|
|
124
|
+
if (!ownsFamily || windows.length) quota = { ...quota, customWindows: windows };
|
|
125
|
+
}
|
|
110
126
|
const publicationGeneration = anthropicCooldownGeneration(accountId);
|
|
127
|
+
const publicationFamilyGeneration = anthropicFamilyQuotaGeneration(accountId);
|
|
111
128
|
afterSettlementForTests?.();
|
|
112
129
|
return {
|
|
113
130
|
quota,
|
|
114
131
|
isCurrent: () => probe.isCurrentCredential()
|
|
115
132
|
&& anthropicCooldownGeneration(accountId) === publicationGeneration
|
|
133
|
+
&& anthropicFamilyQuotaGeneration(accountId) === publicationFamilyGeneration
|
|
116
134
|
&& mayPublish(),
|
|
117
135
|
};
|
|
118
136
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** 7d_oi attribution is fixture-confirmed for Fable; it is not a future-family wildcard. */
|
|
2
|
+
import type { ProviderQuotaWindow } from "../quota-types";
|
|
3
|
+
export function parseAnthropicFamilyHeaders(headers: Headers, now: number, status?: number): ProviderQuotaWindow[] {
|
|
4
|
+
const value = headers.get("anthropic-ratelimit-unified-7d_oi-utilization")?.trim();
|
|
5
|
+
const rejected = status === 429 && headers.get("anthropic-ratelimit-unified-7d_oi-status")?.trim() === "rejected";
|
|
6
|
+
const numeric = value && /^\d+(?:\.\d+)?$/.test(value) ? Number(value) : undefined;
|
|
7
|
+
// The observed overage fixture is 1.01; spent evidence clamps to the display ceiling.
|
|
8
|
+
const percent = numeric !== undefined && Number.isFinite(numeric) && numeric >= 0 && numeric <= 1.01
|
|
9
|
+
? Math.min(100, Math.round(numeric * 10_000) / 100) : rejected ? 100 : undefined;
|
|
10
|
+
if (percent === undefined) return [];
|
|
11
|
+
const rawReset = headers.get("anthropic-ratelimit-unified-7d_oi-reset")?.trim();
|
|
12
|
+
const resetAt = rawReset && /^\d+(?:\.\d+)?$/.test(rawReset) ? Number(rawReset) * 1000 : undefined;
|
|
13
|
+
return [{ label: "Fable", scope: "model", percent, passiveObservedAt: now, ...(rejected ? { rejected: true as const } : {}),
|
|
14
|
+
...(resetAt !== undefined && resetAt > 0 && Number.isFinite(new Date(resetAt).getTime()) ? { resetAt } : {}) }];
|
|
15
|
+
}
|
|
16
|
+
export function mergeAnthropicFamilyWindows(previous: ProviderQuotaWindow[] = [], observed: ProviderQuotaWindow[] = []): ProviderQuotaWindow[] {
|
|
17
|
+
return [...previous.filter(window => !observed.some(update => update.label === window.label && update.scope === window.scope)), ...observed];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const enumerated = new WeakSet<object>();
|
|
21
|
+
export function markAnthropicFamilyEnumeration<T extends object>(quota: T, authoritative: boolean): T {
|
|
22
|
+
if (authoritative) enumerated.add(quota);
|
|
23
|
+
return quota;
|
|
24
|
+
}
|
|
25
|
+
export function hasAnthropicFamilyEnumeration(quota: object): boolean { return enumerated.has(quota); }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { markAnthropicFamilyEnumeration } from "./anthropic-family-headers";
|
|
1
2
|
import { effectiveCodexAuthAccountId, fetchMainAccountInfoSnapshot, listCodexAuthAccountsSnapshot } from "../../codex/auth-api";
|
|
2
3
|
import { MAIN_CODEX_ACCOUNT_ID } from "../../codex/main-account";
|
|
3
4
|
import { getValidAccessToken } from "../../oauth";
|
|
@@ -311,7 +312,10 @@ async function readAnthropicUsageQuota(accessToken: string): Promise<ProviderQuo
|
|
|
311
312
|
updatedAt: Date.now(),
|
|
312
313
|
};
|
|
313
314
|
// Empty / schema-changed payloads must not cache as "success with no bars".
|
|
314
|
-
return hasQuotaRows(quota) ? quota
|
|
315
|
+
return hasQuotaRows(quota) ? markAnthropicFamilyEnumeration(quota, Array.isArray(body.limits) && body.limits.every(raw => {
|
|
316
|
+
const kind = asRecord(raw)?.kind;
|
|
317
|
+
return kind === "session" || kind === "weekly_all" || kind === "weekly_scoped" && parseClaudeLimit(raw) !== null;
|
|
318
|
+
})) : null;
|
|
315
319
|
}
|
|
316
320
|
|
|
317
321
|
/**
|
|
@@ -30,6 +30,10 @@ export interface ProviderQuotaWindow {
|
|
|
30
30
|
* provider-wide group named "Opus" there would otherwise be mistaken for a per-model window.
|
|
31
31
|
*/
|
|
32
32
|
scope?: "model";
|
|
33
|
+
/** Passive family readings have their own freshness clock, independent of active probes. */
|
|
34
|
+
passiveObservedAt?: number;
|
|
35
|
+
/** Upstream refusal evidence, distinct from a measured utilization counter. */
|
|
36
|
+
rejected?: true;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export interface ProviderQuotaCreditsUsd {
|
|
@@ -157,6 +157,24 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
157
157
|
// still advertises image for noVision members so Codex can attach (sidecar option B).
|
|
158
158
|
noVisionModels: [...CURSOR_NO_VISION_MODELS],
|
|
159
159
|
},
|
|
160
|
+
{
|
|
161
|
+
id: "zed",
|
|
162
|
+
label: "Zed Hosted AI (experimental)",
|
|
163
|
+
adapter: "zed",
|
|
164
|
+
baseUrl: "https://cloud.zed.dev",
|
|
165
|
+
authKind: "oauth",
|
|
166
|
+
featured: false,
|
|
167
|
+
dashboardPreset: true,
|
|
168
|
+
models: [],
|
|
169
|
+
liveModels: true,
|
|
170
|
+
defaultModel: "auto",
|
|
171
|
+
modelDiscovery: {
|
|
172
|
+
url: "https://cloud.zed.dev/models",
|
|
173
|
+
maxResponseBytes: 4 * 1024 * 1024,
|
|
174
|
+
maxModels: 2_000,
|
|
175
|
+
},
|
|
176
|
+
note: "Experimental and unofficial Zed Hosted AI bridge, not endorsed by Zed. Use at your own risk: it consumes the signed-in Zed account's hosted-model entitlement and may be outside Zed's terms, and Zed may limit or suspend the account.",
|
|
177
|
+
},
|
|
160
178
|
{
|
|
161
179
|
// The canonical Cognition account provider, after absorbing `devin-cli`
|
|
162
180
|
// (devlog/_plan/260913_devin_provider_merge). The two ids were the same
|
|
@@ -1143,6 +1143,61 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
1143
1143
|
modelInputModalities: OPPER_MODEL_INPUT_MODALITIES,
|
|
1144
1144
|
note: "EU-hosted AI gateway: one OpenAI-compatible endpoint and one key in front of 30+ providers. Bare model ids are pools (claude-sonnet-4-6, gpt-5.5) and Opper picks the route per request; vendor/model ids (anthropic/claude-sonnet-4-6) pin one provider. The catalogue is discovered live from /v3/compat/models with your key; the public list is at opper.ai/models. Token rates are the model providers' rates with no markup; Opper charges a 3% fee when you buy credits.",
|
|
1145
1145
|
},
|
|
1146
|
+
{
|
|
1147
|
+
// Sionic AI public contract and live streams/tools/images checked 2026-10-02:
|
|
1148
|
+
// https://opengateway.ai/docs/reference/endpoints/models
|
|
1149
|
+
// https://opengateway.ai/docs/reference/endpoints/responses
|
|
1150
|
+
id: "opengateway",
|
|
1151
|
+
label: "OpenGateway",
|
|
1152
|
+
adapter: "openai-chat",
|
|
1153
|
+
baseUrl: "https://apis.opengateway.ai/v1",
|
|
1154
|
+
authKind: "key",
|
|
1155
|
+
dashboardUrl: "https://opengateway.ai/api-keys",
|
|
1156
|
+
liveModels: true,
|
|
1157
|
+
// GET /v1/models is public and answers 200 for any Bearer value, so it cannot verify a key.
|
|
1158
|
+
apiKeyValidation: "unknown",
|
|
1159
|
+
preserveCustomDestination: true,
|
|
1160
|
+
// Both cold-start seeds are active Sionic-served vision models with 1M context.
|
|
1161
|
+
// GLM is the default, not a quality ranking: a verified, large-output coding seed.
|
|
1162
|
+
defaultModel: "z-ai/glm-5.3-flash-ultrafast",
|
|
1163
|
+
models: ["z-ai/glm-5.3-flash-ultrafast", "deepseek/deepseek-v4.1-flash-ultrafast", "openai/o3-pro"],
|
|
1164
|
+
modelContextWindows: {
|
|
1165
|
+
"z-ai/glm-5.3-flash-ultrafast": 1_000_000,
|
|
1166
|
+
"deepseek/deepseek-v4.1-flash-ultrafast": 1_000_000,
|
|
1167
|
+
"openai/o3-pro": 200_000,
|
|
1168
|
+
},
|
|
1169
|
+
modelMaxOutputTokens: { "z-ai/glm-5.3-flash-ultrafast": 131_072 },
|
|
1170
|
+
modelInputModalities: {
|
|
1171
|
+
"z-ai/glm-5.3-flash-ultrafast": ["text", "image"],
|
|
1172
|
+
"deepseek/deepseek-v4.1-flash-ultrafast": ["text", "image"],
|
|
1173
|
+
"openai/o3-pro": ["text"],
|
|
1174
|
+
},
|
|
1175
|
+
noVisionModels: ["openai/o3-pro"],
|
|
1176
|
+
// Chat is the broad wire (77/80 active rows). o3-pro is Responses-only, on all
|
|
1177
|
+
// inbounds. Only the two live-verified Sionic models bypass translation for Codex.
|
|
1178
|
+
// Other OpenAI/Anthropic/Gemini rows stay on Chat until their Responses tool replay
|
|
1179
|
+
// is independently verified; endpoint availability alone is not conformance evidence.
|
|
1180
|
+
modelWireDefaults: {
|
|
1181
|
+
"openai/o3-pro": "openai-responses",
|
|
1182
|
+
"z-ai/glm-5.3-flash-ultrafast": { wire: "openai-responses", inbound: ["responses"] },
|
|
1183
|
+
"deepseek/deepseek-v4.1-flash-ultrafast": { wire: "openai-responses", inbound: ["responses"] },
|
|
1184
|
+
},
|
|
1185
|
+
modelDiscovery: {
|
|
1186
|
+
path: "models", // Public GET; lower-case ?status=active returns 400, so filter locally.
|
|
1187
|
+
// Admit a row only on a wire it is served on: the Chat default needs chat_completions,
|
|
1188
|
+
// and Responses-only rows are admitted only when pinned to Responses above. A future
|
|
1189
|
+
// Responses-only row stays hidden until it is pinned, rather than 404ing on Chat.
|
|
1190
|
+
filter: {
|
|
1191
|
+
allOf: [{ path: ["status"], equalsAny: ["active"] }],
|
|
1192
|
+
anyOf: [
|
|
1193
|
+
{ path: ["endpoints"], containsAny: ["chat_completions"] },
|
|
1194
|
+
{ path: ["id"], equalsAny: ["openai/o3-pro"] },
|
|
1195
|
+
],
|
|
1196
|
+
},
|
|
1197
|
+
preferFirst: [{ path: ["providers", "*", "id"], containsAny: ["sionic-ai"] }],
|
|
1198
|
+
},
|
|
1199
|
+
note: "Sionic AI's OpenAI-compatible gateway. Public live discovery lists active models served on Chat Completions (plus pinned Responses-only rows), with Sionic-served ultrafast models first. The public catalog cannot validate keys.",
|
|
1200
|
+
},
|
|
1146
1201
|
{
|
|
1147
1202
|
// Public contract checked 2026-09-29: https://docs.tokenlab.sh/api-reference/models/list-models
|
|
1148
1203
|
// A supplied key is validated and scopes the catalog; anonymous discovery is also public.
|
|
@@ -30,6 +30,7 @@ export interface ResponsesTerminalRepairPolicy {
|
|
|
30
30
|
|
|
31
31
|
export type ProviderModelDiscoveryScalar = string | number | boolean;
|
|
32
32
|
|
|
33
|
+
/** Path segment `*` projects at most 256 array elements; containsAny/All match projected scalars exactly. */
|
|
33
34
|
export type ProviderModelDiscoveryPredicate =
|
|
34
35
|
| {
|
|
35
36
|
path: readonly string[];
|
|
@@ -70,6 +71,8 @@ interface ProviderModelDiscoverySharedSpec {
|
|
|
70
71
|
idField?: string;
|
|
71
72
|
/** Declarative eligibility rules evaluated against each untrusted model row. */
|
|
72
73
|
filter?: ProviderModelDiscoveryFilter;
|
|
74
|
+
/** Stable partition: admitted original rows matching ANY predicate precede other rows. Registry-only. */
|
|
75
|
+
preferFirst?: readonly ProviderModelDiscoveryPredicate[];
|
|
73
76
|
/** Optional lower byte ceiling; the process-wide hard ceiling still wins. */
|
|
74
77
|
maxResponseBytes?: number;
|
|
75
78
|
/** Optional lower raw-row ceiling; the process-wide hard ceiling still wins. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FastWire, OcxParsedRequest, OcxProviderConfig } from "../types";
|
|
1
|
+
import type { FastWire, OcxParsedRequest, OcxProviderConfig, TierDecision } from "../types";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Grok OAuth Fast is a serving lane, not a tier.
|
|
@@ -35,6 +35,16 @@ function modelVariantFastWire(variant: string): FastWire {
|
|
|
35
35
|
return { kind: "model-variant", canonicalToWire: { priority: variant }, foreignCallerTiers: "drop" };
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** Shared by admission preview and final serialization; explicit operator wires stay authoritative. */
|
|
39
|
+
export function xaiOauthFastModelForDecision(
|
|
40
|
+
route: { providerName: string; provider: Pick<OcxProviderConfig, "authMode" | "fastWire">; modelId: string },
|
|
41
|
+
decision: TierDecision | undefined,
|
|
42
|
+
): string | undefined {
|
|
43
|
+
return decision?.kind === "set" && route.provider.fastWire === undefined
|
|
44
|
+
? xaiOauthFastModel(route.providerName, route.provider, route.modelId)
|
|
45
|
+
: undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
39
49
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
40
50
|
}
|
|
@@ -58,9 +68,7 @@ export function applyXaiOauthFastModel(
|
|
|
58
68
|
// The lane switch replaces the registry's service-tier Fast only. The xai registry entry declares no
|
|
59
69
|
// FastWire, so a provider-level `fastWire` is always the operator's own (service-tier.ts reads it
|
|
60
70
|
// first): that decision carries a wire value they verified, and it is sent unchanged.
|
|
61
|
-
const variant = parsed.options.tierDecision
|
|
62
|
-
? xaiOauthFastModel(route.providerName, route.provider, route.modelId)
|
|
63
|
-
: undefined;
|
|
71
|
+
const variant = xaiOauthFastModelForDecision(route, parsed.options.tierDecision);
|
|
64
72
|
if (!variant) {
|
|
65
73
|
if (previous === undefined) return;
|
|
66
74
|
delete parsed._wireModelOverride;
|