@bitkyc08/opencodex 2.52.0-preview.20260911 → 2.52.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/gui/dist/assets/index-CWXut3rG.js +115 -0
- package/gui/dist/assets/index-EdoPnm9_.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/devin.svg +49 -0
- package/gui/dist/provider-icons/omo.svg +42 -0
- package/package.json +3 -1
- package/src/AGENTS.md +1 -1
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +0 -1
- package/src/adapters/command-code.ts +0 -1
- package/src/adapters/cursor/checkpoint-store.ts +37 -0
- package/src/adapters/cursor/live-transport.ts +74 -2
- package/src/adapters/cursor.ts +6 -0
- package/src/adapters/devin/cloud-direct/auth.ts +264 -0
- package/src/adapters/devin/cloud-direct/catalog.ts +306 -0
- package/src/adapters/devin/cloud-direct/chat.ts +1274 -0
- package/src/adapters/devin/cloud-direct/index.ts +65 -0
- package/src/adapters/devin/cloud-direct/metadata.ts +134 -0
- package/src/adapters/devin/cloud-direct/wire.ts +206 -0
- package/src/adapters/devin/live-models.ts +133 -0
- package/src/adapters/devin-cli/acp.ts +204 -0
- package/src/adapters/devin-cli/adapter.ts +345 -0
- package/src/adapters/devin-cli/binary.ts +69 -0
- package/src/adapters/devin-cli/models.ts +57 -0
- package/src/adapters/devin.ts +326 -0
- package/src/adapters/google.ts +1 -1
- package/src/adapters/openai-chat.ts +31 -10
- package/src/adapters/openai-responses.ts +16 -1
- package/src/adapters/registry.ts +25 -1
- package/src/bridge.ts +8 -2
- package/src/claude/inbound-cache-stabilize.ts +130 -0
- package/src/claude/inbound.ts +45 -5
- package/src/cli/account-auth.ts +60 -1
- package/src/cli/account-extended.ts +23 -30
- package/src/cli/account.ts +2 -1
- package/src/cli/capabilities.ts +50 -8
- package/src/cli/config-command.ts +2 -2
- package/src/cli/dispatch.ts +14 -2
- package/src/cli/export-command.ts +11 -25
- package/src/cli/help.ts +2 -2
- package/src/cli/opencode.ts +5 -0
- package/src/cli/registry.ts +15 -4
- package/src/clients/config-export/cline.ts +71 -0
- package/src/clients/config-export/contracts.ts +10 -1
- package/src/clients/config-export/model-metadata.ts +33 -0
- package/src/clients/config-export/zcode.ts +23 -12
- package/src/clients/config-export.ts +156 -4
- package/src/codex/account-store.ts +7 -2
- package/src/codex/auth-context.ts +1 -1
- package/src/codex/catalog/effort.ts +4 -3
- package/src/codex/catalog/metadata.ts +8 -4
- package/src/codex/catalog/native-models.ts +24 -4
- package/src/codex/catalog/parsing.ts +19 -1
- package/src/codex/catalog/provider-fetch.ts +57 -0
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/context-compat.ts +97 -0
- package/src/codex/context-owner.ts +201 -0
- package/src/codex/history-provider.ts +161 -14
- package/src/codex/inject-coordination.ts +3 -2
- package/src/codex/inject.ts +128 -9
- package/src/codex/pool-rotation.ts +8 -292
- package/src/codex/quota.ts +41 -12
- package/src/codex/retired-model-migration.ts +41 -0
- package/src/codex/routing.ts +327 -22
- package/src/codex/warmup.ts +2 -2
- package/src/combos/failover.ts +5 -0
- package/src/combos/resolve.ts +11 -15
- package/src/config.ts +46 -14
- package/src/generated/compatibility-version.json +293 -113
- package/src/grok/grpc-web.ts +120 -0
- package/src/grok/reset-coupon-ledger.ts +139 -0
- package/src/grok/reset-coupons.ts +278 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/cline-document.ts +73 -0
- package/src/integrations/cline-io.ts +149 -0
- package/src/integrations/cline-transaction.ts +42 -0
- package/src/integrations/config-io.ts +21 -2
- package/src/integrations/journal.ts +43 -1
- package/src/integrations/omp-yaml-source.ts +123 -4
- package/src/integrations/ownership-policy.ts +7 -0
- package/src/integrations/ownership.ts +36 -0
- package/src/integrations/registry.ts +27 -0
- package/src/integrations/state.ts +5 -2
- package/src/integrations/store.ts +6 -0
- package/src/integrations/writer.ts +53 -9
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/abort.ts +36 -0
- package/src/lib/local-destinations.ts +1 -1
- package/src/lib/upstream-retry.ts +36 -1
- package/src/oauth/account-quota-rank.ts +11 -0
- package/src/oauth/callback-server.ts +10 -5
- package/src/oauth/devin/api-base.ts +63 -0
- package/src/oauth/devin/login.ts +1 -0
- package/src/oauth/devin/register-user.ts +186 -0
- package/src/oauth/devin/types.ts +71 -0
- package/src/oauth/devin-cli.ts +149 -0
- package/src/oauth/devin.ts +170 -0
- package/src/oauth/generic-account-failover.ts +169 -1
- package/src/oauth/index.ts +20 -1
- package/src/oauth/login-cli.ts +19 -5
- package/src/oauth/pool-kernel.ts +321 -0
- package/src/oauth/pool-settings-capability.ts +127 -9
- package/src/oauth/store.ts +7 -3
- package/src/oauth/token-guardian.ts +1 -1
- package/src/providers/codebuddy-models.ts +0 -3
- package/src/providers/command-code-efforts.ts +23 -4
- package/src/providers/default-aliases.ts +4 -0
- package/src/providers/derive.ts +8 -0
- package/src/providers/devin-cli-authmode-migration.ts +57 -0
- package/src/providers/key-failover.ts +175 -0
- package/src/providers/model-rename-startup.ts +21 -1
- package/src/providers/qoder-models.ts +0 -1
- package/src/providers/quota-key-accounts.ts +50 -0
- package/src/providers/quota-routing-cache.ts +65 -6
- package/src/providers/quota-types.ts +7 -0
- package/src/providers/quota.ts +113 -30
- package/src/providers/registry.ts +237 -77
- package/src/providers/stale-context-window-migration.ts +92 -0
- package/src/providers/zai-responses-migration.ts +45 -0
- package/src/quota/reset-observer.ts +2 -1
- package/src/quota/reset-seen-store.ts +9 -1
- package/src/remote-control/crypto.ts +442 -0
- package/src/remote-control/host.ts +175 -0
- package/src/remote-control/index.ts +100 -0
- package/src/remote-control/protocol.ts +200 -0
- package/src/remote-control/relay.ts +162 -0
- package/src/remote-control/workspace-agent-protocol.ts +246 -0
- package/src/remote-control/workspace-rpc-framing.ts +128 -0
- package/src/remote-control/workspace-tools.ts +237 -0
- package/src/remote-control/workspace-utf8.ts +24 -0
- package/src/responses/custom-tool-compat.ts +23 -0
- package/src/router.ts +6 -1
- package/src/routing/compatibility/behavior.ts +3 -0
- package/src/server/adapter-resolve.ts +6 -2
- package/src/server/auth-cors.ts +71 -6
- package/src/server/chat-completions.ts +5 -3
- package/src/server/chat-native.ts +9 -0
- package/src/server/claude-messages.ts +25 -30
- package/src/server/context-history.ts +207 -0
- package/src/server/images.ts +28 -1
- package/src/server/index.ts +42 -22
- package/src/server/live.ts +2 -1
- package/src/server/management/agent-settings-routes.ts +7 -1
- package/src/server/management/config-routes.ts +3 -3
- package/src/server/management/grok-coupon-routes.ts +287 -0
- package/src/server/management/integration-routes.ts +6 -1
- package/src/server/management/oauth-account-routes.ts +124 -7
- package/src/server/management/provider-routes.ts +77 -2
- package/src/server/management/route-registry.ts +6 -1
- package/src/server/management-api.ts +7 -0
- package/src/server/request-log.ts +4 -0
- package/src/server/responses/codex-ws-exchange.ts +90 -14
- package/src/server/responses/codex-ws-wire.ts +51 -1
- package/src/server/responses/compact.ts +8 -1
- package/src/server/responses/core.ts +188 -22
- package/src/server/responses/ws-upstream.ts +1 -1
- package/src/server/responses-undeclared-tool-guard.ts +261 -12
- package/src/server/zai-responses-startup.ts +21 -0
- package/src/types/config.ts +33 -3
- package/src/types/provider.ts +54 -4
- package/src/types/request.ts +1 -1
- package/src/types/tools.ts +36 -6
- package/src/update/job.ts +33 -11
- package/src/vision/plan.ts +40 -37
- package/src/web-search/index.ts +1 -0
- package/gui/dist/assets/index-BoBRSehJ.css +0 -1
- package/gui/dist/assets/index-Dx0xv2EA.js +0 -115
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Derived from rsvedant/opencode-windsurf-auth (src/cloud-direct/), MIT licensed,
|
|
3
|
+
* Copyright (c) 2026 Vedant. The full notice is in ./index.ts.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Per-account model catalog from Cognition's `GetCascadeModelConfigs`.
|
|
7
|
+
*
|
|
8
|
+
* Why this exists — issue #14:
|
|
9
|
+
* The cloud's `GetChatMessage` returns a single Connect-streaming EOS frame
|
|
10
|
+
* containing `{"error":{"code":"permission_denied","message":"an internal
|
|
11
|
+
* error occurred (trace ID: <hex>)"}}` whenever the caller's account tier
|
|
12
|
+
* does not include the requested `model_uid`. Reproduced byte-identical on a
|
|
13
|
+
* `TEAMS_TIER_DEVIN_FREE` account for every Anthropic/Gemini/Premium UID
|
|
14
|
+
* (only `swe-1-6-slow` streamed a real reply). The user-facing message is
|
|
15
|
+
* indistinguishable from a transient server fault — issue #14's reporter
|
|
16
|
+
* spent multiple sessions guessing.
|
|
17
|
+
*
|
|
18
|
+
* The pre-flight here checks the per-account catalog (`disabled` flag on
|
|
19
|
+
* `ClientModelConfig` field #4) BEFORE we spend a roundtrip on a request
|
|
20
|
+
* the cloud will refuse. When the lookup fails (network, auth, schema
|
|
21
|
+
* drift) we silently fall back to the chat path so a transient catalog
|
|
22
|
+
* outage can't take chat down with it.
|
|
23
|
+
*
|
|
24
|
+
* Schema (verified against the bundled `extension.js`,
|
|
25
|
+
* `exa.codeium_common_pb.ClientModelConfig`):
|
|
26
|
+
*
|
|
27
|
+
* GetCascadeModelConfigsResponse {
|
|
28
|
+
* #1 client_model_configs: repeated ClientModelConfig
|
|
29
|
+
* }
|
|
30
|
+
* ClientModelConfig {
|
|
31
|
+
* #1 label string
|
|
32
|
+
* #4 disabled bool ← the gate this module reads
|
|
33
|
+
* #22 model_uid string ← what `GetChatMessage` accepts
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* Disabled semantics: TRUE means "this UID exists in the catalog but the
|
|
37
|
+
* caller's account/tier cannot run inference against it." BYOK models
|
|
38
|
+
* surface as `disabled: false` so users with their own provider keys still
|
|
39
|
+
* pass through — the only way they fail at chat time is a missing key,
|
|
40
|
+
* which surfaces with a different message.
|
|
41
|
+
*
|
|
42
|
+
* Cache: per (apiServerUrl, apiKey) for {@link CATALOG_TTL_MS}. Cognition
|
|
43
|
+
* doesn't bump catalog entries mid-session in normal operation, so a 10-min
|
|
44
|
+
* TTL trades one extra roundtrip per ~10 min for clear errors on every chat.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import * as crypto from 'crypto';
|
|
48
|
+
import { buildMetadata } from './metadata.js';
|
|
49
|
+
import { getCachedUserJwt } from './auth.js';
|
|
50
|
+
import { encodeMessage, iterFields } from './wire.js';
|
|
51
|
+
import { resolveDevinApiBaseUrl } from '../../../oauth/devin/api-base.js';
|
|
52
|
+
|
|
53
|
+
/** 10 minutes — see header. */
|
|
54
|
+
const CATALOG_TTL_MS = 10 * 60 * 1000;
|
|
55
|
+
|
|
56
|
+
/** Catalog endpoint inactivity timeout. Cognition responds in <500ms steady-state. */
|
|
57
|
+
const CATALOG_FETCH_TIMEOUT_MS = 10_000;
|
|
58
|
+
|
|
59
|
+
export interface ModelCatalogEntry {
|
|
60
|
+
/** Cloud-side `model_uid` (e.g. `claude-opus-4-7-medium`). */
|
|
61
|
+
modelUid: string;
|
|
62
|
+
/** Human label (e.g. `Claude Opus 4.7 Medium`) — used in error messages. */
|
|
63
|
+
label: string;
|
|
64
|
+
/** True when the caller's account tier cannot use this UID for chat. */
|
|
65
|
+
disabled: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Maximum input tokens the account may send this model, from
|
|
68
|
+
* `ClientModelConfig` field #18.
|
|
69
|
+
*
|
|
70
|
+
* Cognition publishes no context-window numbers anywhere: not in the Devin
|
|
71
|
+
* CLI or Desktop model pages, not in the SWE-2 or SWE-1.7 announcements, and
|
|
72
|
+
* not in the Windsurf model reference, which has no such table. The only
|
|
73
|
+
* numbers on those pages are long-context PRICING thresholds, which are a
|
|
74
|
+
* different quantity. That makes this field the single first-party source,
|
|
75
|
+
* and it is per account rather than per model id.
|
|
76
|
+
*
|
|
77
|
+
* Absent when the entry omits the field, which is how a future schema change
|
|
78
|
+
* degrades: the caller keeps its static fallback instead of reporting zero.
|
|
79
|
+
*/
|
|
80
|
+
contextWindow?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface CacheEntry {
|
|
84
|
+
/** Lookup keyed by `model_uid`. */
|
|
85
|
+
byUid: Map<string, ModelCatalogEntry>;
|
|
86
|
+
fetchedAt: number;
|
|
87
|
+
/** Cache key components, captured for invalidation/log purposes. */
|
|
88
|
+
apiKey: string;
|
|
89
|
+
host: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let cached: CacheEntry | null = null;
|
|
93
|
+
let inFlight: Promise<CacheEntry> | null = null;
|
|
94
|
+
let inFlightKey: string | null = null;
|
|
95
|
+
// Bumped on clearCachedCatalog so an in-flight fetch racing with a clear
|
|
96
|
+
// can't repopulate the cache with a just-invalidated catalog.
|
|
97
|
+
let cacheEpoch = 0;
|
|
98
|
+
|
|
99
|
+
function flightKey(apiKey: string, host: string): string {
|
|
100
|
+
return `${host}\x1f${apiKey}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parse a GetCascadeModelConfigsResponse buffer into a UID-keyed map.
|
|
105
|
+
* A malformed catalog returns an empty map.
|
|
106
|
+
*/
|
|
107
|
+
export function parseCatalogBuffer(buf: Buffer, apiKey: string, host: string): CacheEntry {
|
|
108
|
+
// GetCascadeModelConfigsResponse #1 (repeated ClientModelConfig)
|
|
109
|
+
const byUid = new Map<string, ModelCatalogEntry>();
|
|
110
|
+
for (const f of iterFields(buf)) {
|
|
111
|
+
if (f.num !== 1 || f.wire !== 2 || !Buffer.isBuffer(f.value)) continue;
|
|
112
|
+
let label = '';
|
|
113
|
+
let modelUid = '';
|
|
114
|
+
let disabled = false;
|
|
115
|
+
let contextWindow = 0;
|
|
116
|
+
for (const sf of iterFields(f.value as Buffer)) {
|
|
117
|
+
if (sf.num === 1 && sf.wire === 2 && Buffer.isBuffer(sf.value)) {
|
|
118
|
+
label = (sf.value as Buffer).toString('utf8');
|
|
119
|
+
} else if (sf.num === 4 && sf.wire === 0) {
|
|
120
|
+
// #4 = disabled (bool, varint 0/1)
|
|
121
|
+
disabled = sf.value === 1n;
|
|
122
|
+
} else if (sf.num === 18 && sf.wire === 0) {
|
|
123
|
+
// #18 = max input tokens. Identified by dumping a live catalog and
|
|
124
|
+
// reading the varints back against models whose windows are known from
|
|
125
|
+
// their upstream vendors: 1000000 on the Claude and GPT rows, 1048576
|
|
126
|
+
// on Gemini/Kimi/GLM, 500000 on Grok, 262000 on swe-2.
|
|
127
|
+
contextWindow = Number(sf.value);
|
|
128
|
+
} else if (sf.num === 22 && sf.wire === 2 && Buffer.isBuffer(sf.value)) {
|
|
129
|
+
modelUid = (sf.value as Buffer).toString('utf8');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (modelUid.length > 0) {
|
|
133
|
+
byUid.set(modelUid, {
|
|
134
|
+
modelUid,
|
|
135
|
+
label: label || modelUid,
|
|
136
|
+
disabled,
|
|
137
|
+
...(contextWindow > 0 ? { contextWindow } : {}),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { byUid, fetchedAt: Date.now(), apiKey, host };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Fetch the cascade model catalog for `(apiKey, host)` and parse the
|
|
146
|
+
* subset of `ClientModelConfig` we care about into a UID-keyed map.
|
|
147
|
+
*
|
|
148
|
+
* Throws on transport/auth failure so the caller can decide whether to fall
|
|
149
|
+
* back to "skip pre-flight". Does NOT throw on an unexpected response body —
|
|
150
|
+
* a malformed catalog returns an empty map, treated the same as "model not
|
|
151
|
+
* listed" by the chat pre-flight.
|
|
152
|
+
*
|
|
153
|
+
* Uses only an internal timeout — caller cancellation is handled by
|
|
154
|
+
* getCachedCatalog racing each caller's signal against the shared promise.
|
|
155
|
+
*/
|
|
156
|
+
async function fetchCatalog(apiKey: string, host: string): Promise<CacheEntry> {
|
|
157
|
+
const userJwt = await getCachedUserJwt(apiKey, host);
|
|
158
|
+
|
|
159
|
+
const metadata = buildMetadata({
|
|
160
|
+
apiKey,
|
|
161
|
+
userJwt,
|
|
162
|
+
sessionId: crypto.randomUUID(),
|
|
163
|
+
requestId: BigInt(Date.now()),
|
|
164
|
+
triggerId: crypto.randomUUID(),
|
|
165
|
+
});
|
|
166
|
+
// GetCascadeModelConfigsRequest { metadata: Metadata } — Metadata is #1.
|
|
167
|
+
const reqBody = encodeMessage(1, metadata);
|
|
168
|
+
|
|
169
|
+
// Internal 10s timeout so a stalled catalog endpoint can't deadlock chat.
|
|
170
|
+
// The shared fetch uses only this internal timeout — caller cancellation is
|
|
171
|
+
// handled by racing each caller's signal against the shared promise in
|
|
172
|
+
// getCachedCatalog, so one caller's abort never propagates to unrelated
|
|
173
|
+
// callers sharing the same in-flight fetch.
|
|
174
|
+
const ac = new AbortController();
|
|
175
|
+
const timer = setTimeout(
|
|
176
|
+
() => ac.abort(new Error(`catalog: fetch timeout (${CATALOG_FETCH_TIMEOUT_MS}ms)`)),
|
|
177
|
+
CATALOG_FETCH_TIMEOUT_MS,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
let resp: Response;
|
|
181
|
+
try {
|
|
182
|
+
resp = await fetch(`${resolveDevinApiBaseUrl(host)}/exa.api_server_pb.ApiServerService/GetCascadeModelConfigs`, {
|
|
183
|
+
method: 'POST',
|
|
184
|
+
headers: { 'Content-Type': 'application/proto', 'Connect-Protocol-Version': '1' },
|
|
185
|
+
body: new Uint8Array(reqBody),
|
|
186
|
+
// This body carries the api_key; a redirect would replay it elsewhere.
|
|
187
|
+
redirect: 'error',
|
|
188
|
+
signal: ac.signal,
|
|
189
|
+
});
|
|
190
|
+
if (!resp.ok) {
|
|
191
|
+
// Status only: the error body can quote the api_key-bearing request.
|
|
192
|
+
throw new Error(`GetCascadeModelConfigs failed (HTTP ${resp.status})`);
|
|
193
|
+
}
|
|
194
|
+
// Read the body BEFORE clearing the timeout — fetch resolves on headers,
|
|
195
|
+
// not body completion. A stalled body would otherwise block indefinitely.
|
|
196
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
197
|
+
return parseCatalogBuffer(buf, apiKey, host);
|
|
198
|
+
} finally {
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get the cached catalog for `(apiKey, host)`, fetching when missing or stale.
|
|
205
|
+
*
|
|
206
|
+
* Concurrent callers for the SAME (apiKey, host) share one in-flight fetch
|
|
207
|
+
* (no thundering herd on startup). Concurrent callers for DIFFERENT keys
|
|
208
|
+
* serialise the in-flight slot but only one of them holds it at a time —
|
|
209
|
+
* good enough for opencode's single-account-at-a-time usage pattern.
|
|
210
|
+
*
|
|
211
|
+
* Returns `null` on fetch failure (network, transient 5xx, auth issue). The
|
|
212
|
+
* caller treats `null` as "skip pre-flight and let the chat path surface the
|
|
213
|
+
* server-side error itself."
|
|
214
|
+
*/
|
|
215
|
+
export async function getCachedCatalog(
|
|
216
|
+
apiKey: string,
|
|
217
|
+
host: string,
|
|
218
|
+
signal?: AbortSignal,
|
|
219
|
+
): Promise<CacheEntry | null> {
|
|
220
|
+
if (cached && cached.apiKey === apiKey && cached.host === host) {
|
|
221
|
+
if (Date.now() - cached.fetchedAt < CATALOG_TTL_MS) {
|
|
222
|
+
return cached;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const key = flightKey(apiKey, host);
|
|
227
|
+
// Race the caller's signal against the shared promise so one caller's
|
|
228
|
+
// cancellation doesn't propagate to unrelated callers sharing the fetch.
|
|
229
|
+
const raceSignal = <T>(p: Promise<T>): Promise<T> =>
|
|
230
|
+
signal
|
|
231
|
+
? Promise.race([
|
|
232
|
+
p,
|
|
233
|
+
new Promise<T>((_, reject) => {
|
|
234
|
+
if (signal.aborted) reject(signal.reason);
|
|
235
|
+
else signal.addEventListener('abort', () => reject(signal.reason), { once: true });
|
|
236
|
+
}),
|
|
237
|
+
])
|
|
238
|
+
: p;
|
|
239
|
+
|
|
240
|
+
if (inFlight && inFlightKey === key) {
|
|
241
|
+
try {
|
|
242
|
+
return await raceSignal(inFlight);
|
|
243
|
+
} catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const promise = fetchCatalog(apiKey, host);
|
|
249
|
+
inFlight = promise;
|
|
250
|
+
inFlightKey = key;
|
|
251
|
+
const epochAtStart = cacheEpoch;
|
|
252
|
+
try {
|
|
253
|
+
const result = await raceSignal(promise);
|
|
254
|
+
if (cacheEpoch === epochAtStart) {
|
|
255
|
+
cached = result;
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
} catch {
|
|
259
|
+
return null;
|
|
260
|
+
} finally {
|
|
261
|
+
if (inFlight === promise) {
|
|
262
|
+
inFlight = null;
|
|
263
|
+
inFlightKey = null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Drop the cached catalog. Call after logout/account switch so a fresh
|
|
270
|
+
* sign-in doesn't see a previous account's allow-list. Bumps the cache
|
|
271
|
+
* epoch so an in-flight fetch racing with this clear can't repopulate
|
|
272
|
+
* the cache with the just-invalidated catalog.
|
|
273
|
+
*/
|
|
274
|
+
export function clearCachedCatalog(): void {
|
|
275
|
+
cached = null;
|
|
276
|
+
inFlight = null;
|
|
277
|
+
inFlightKey = null;
|
|
278
|
+
cacheEpoch++;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Tier-disabled error — thrown by the chat pre-flight when the catalog lists
|
|
283
|
+
* a model as `disabled: true` for this account. The message names the model
|
|
284
|
+
* and points at the plan page, replacing Cognition's opaque
|
|
285
|
+
* "an internal error occurred" trailer.
|
|
286
|
+
*/
|
|
287
|
+
export class ModelNotAvailableError extends Error {
|
|
288
|
+
constructor(
|
|
289
|
+
public readonly modelUid: string,
|
|
290
|
+
public readonly label: string,
|
|
291
|
+
public readonly reason: 'disabled' | 'not_listed',
|
|
292
|
+
) {
|
|
293
|
+
super(
|
|
294
|
+
reason === 'disabled'
|
|
295
|
+
? `Model "${label}" (uid=${modelUid}) is not enabled for your Cognition account. ` +
|
|
296
|
+
`The Cognition catalog returned it with disabled=true — meaning your current plan/tier ` +
|
|
297
|
+
`does not include this model. ` +
|
|
298
|
+
`Check the model picker on https://codeium.com/account, or pick a different model. ` +
|
|
299
|
+
`(This message replaces Cognition's "an internal error occurred" — same root cause.)`
|
|
300
|
+
: `Model uid "${modelUid}" is not listed in the Cognition catalog for your account. ` +
|
|
301
|
+
`Either the UID has been retired upstream or your account/region doesn't serve it. ` +
|
|
302
|
+
`Run \`curl http://127.0.0.1:42100/v1/models\` to see the canonical names your plan accepts.`,
|
|
303
|
+
);
|
|
304
|
+
this.name = 'ModelNotAvailableError';
|
|
305
|
+
}
|
|
306
|
+
}
|