@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.
Files changed (166) hide show
  1. package/gui/dist/assets/index-CWXut3rG.js +115 -0
  2. package/gui/dist/assets/index-EdoPnm9_.css +1 -0
  3. package/gui/dist/index.html +2 -2
  4. package/gui/dist/provider-icons/devin.svg +49 -0
  5. package/gui/dist/provider-icons/omo.svg +42 -0
  6. package/package.json +3 -1
  7. package/src/AGENTS.md +1 -1
  8. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +0 -1
  9. package/src/adapters/command-code.ts +0 -1
  10. package/src/adapters/cursor/checkpoint-store.ts +37 -0
  11. package/src/adapters/cursor/live-transport.ts +74 -2
  12. package/src/adapters/cursor.ts +6 -0
  13. package/src/adapters/devin/cloud-direct/auth.ts +264 -0
  14. package/src/adapters/devin/cloud-direct/catalog.ts +306 -0
  15. package/src/adapters/devin/cloud-direct/chat.ts +1274 -0
  16. package/src/adapters/devin/cloud-direct/index.ts +65 -0
  17. package/src/adapters/devin/cloud-direct/metadata.ts +134 -0
  18. package/src/adapters/devin/cloud-direct/wire.ts +206 -0
  19. package/src/adapters/devin/live-models.ts +133 -0
  20. package/src/adapters/devin-cli/acp.ts +204 -0
  21. package/src/adapters/devin-cli/adapter.ts +345 -0
  22. package/src/adapters/devin-cli/binary.ts +69 -0
  23. package/src/adapters/devin-cli/models.ts +57 -0
  24. package/src/adapters/devin.ts +326 -0
  25. package/src/adapters/google.ts +1 -1
  26. package/src/adapters/openai-chat.ts +31 -10
  27. package/src/adapters/openai-responses.ts +16 -1
  28. package/src/adapters/registry.ts +25 -1
  29. package/src/bridge.ts +8 -2
  30. package/src/claude/inbound-cache-stabilize.ts +130 -0
  31. package/src/claude/inbound.ts +45 -5
  32. package/src/cli/account-auth.ts +60 -1
  33. package/src/cli/account-extended.ts +23 -30
  34. package/src/cli/account.ts +2 -1
  35. package/src/cli/capabilities.ts +50 -8
  36. package/src/cli/config-command.ts +2 -2
  37. package/src/cli/dispatch.ts +14 -2
  38. package/src/cli/export-command.ts +11 -25
  39. package/src/cli/help.ts +2 -2
  40. package/src/cli/opencode.ts +5 -0
  41. package/src/cli/registry.ts +15 -4
  42. package/src/clients/config-export/cline.ts +71 -0
  43. package/src/clients/config-export/contracts.ts +10 -1
  44. package/src/clients/config-export/model-metadata.ts +33 -0
  45. package/src/clients/config-export/zcode.ts +23 -12
  46. package/src/clients/config-export.ts +156 -4
  47. package/src/codex/account-store.ts +7 -2
  48. package/src/codex/auth-context.ts +1 -1
  49. package/src/codex/catalog/effort.ts +4 -3
  50. package/src/codex/catalog/metadata.ts +8 -4
  51. package/src/codex/catalog/native-models.ts +24 -4
  52. package/src/codex/catalog/parsing.ts +19 -1
  53. package/src/codex/catalog/provider-fetch.ts +57 -0
  54. package/src/codex/catalog/sync.ts +1 -1
  55. package/src/codex/context-compat.ts +97 -0
  56. package/src/codex/context-owner.ts +201 -0
  57. package/src/codex/history-provider.ts +161 -14
  58. package/src/codex/inject-coordination.ts +3 -2
  59. package/src/codex/inject.ts +128 -9
  60. package/src/codex/pool-rotation.ts +8 -292
  61. package/src/codex/quota.ts +41 -12
  62. package/src/codex/retired-model-migration.ts +41 -0
  63. package/src/codex/routing.ts +327 -22
  64. package/src/codex/warmup.ts +2 -2
  65. package/src/combos/failover.ts +5 -0
  66. package/src/combos/resolve.ts +11 -15
  67. package/src/config.ts +46 -14
  68. package/src/generated/compatibility-version.json +293 -113
  69. package/src/grok/grpc-web.ts +120 -0
  70. package/src/grok/reset-coupon-ledger.ts +139 -0
  71. package/src/grok/reset-coupons.ts +278 -0
  72. package/src/integrations/catalog-refresh.ts +1 -1
  73. package/src/integrations/cline-document.ts +73 -0
  74. package/src/integrations/cline-io.ts +149 -0
  75. package/src/integrations/cline-transaction.ts +42 -0
  76. package/src/integrations/config-io.ts +21 -2
  77. package/src/integrations/journal.ts +43 -1
  78. package/src/integrations/omp-yaml-source.ts +123 -4
  79. package/src/integrations/ownership-policy.ts +7 -0
  80. package/src/integrations/ownership.ts +36 -0
  81. package/src/integrations/registry.ts +27 -0
  82. package/src/integrations/state.ts +5 -2
  83. package/src/integrations/store.ts +6 -0
  84. package/src/integrations/writer.ts +53 -9
  85. package/src/lab/subject/behavior-fingerprint.ts +1 -1
  86. package/src/lib/abort.ts +36 -0
  87. package/src/lib/local-destinations.ts +1 -1
  88. package/src/lib/upstream-retry.ts +36 -1
  89. package/src/oauth/account-quota-rank.ts +11 -0
  90. package/src/oauth/callback-server.ts +10 -5
  91. package/src/oauth/devin/api-base.ts +63 -0
  92. package/src/oauth/devin/login.ts +1 -0
  93. package/src/oauth/devin/register-user.ts +186 -0
  94. package/src/oauth/devin/types.ts +71 -0
  95. package/src/oauth/devin-cli.ts +149 -0
  96. package/src/oauth/devin.ts +170 -0
  97. package/src/oauth/generic-account-failover.ts +169 -1
  98. package/src/oauth/index.ts +20 -1
  99. package/src/oauth/login-cli.ts +19 -5
  100. package/src/oauth/pool-kernel.ts +321 -0
  101. package/src/oauth/pool-settings-capability.ts +127 -9
  102. package/src/oauth/store.ts +7 -3
  103. package/src/oauth/token-guardian.ts +1 -1
  104. package/src/providers/codebuddy-models.ts +0 -3
  105. package/src/providers/command-code-efforts.ts +23 -4
  106. package/src/providers/default-aliases.ts +4 -0
  107. package/src/providers/derive.ts +8 -0
  108. package/src/providers/devin-cli-authmode-migration.ts +57 -0
  109. package/src/providers/key-failover.ts +175 -0
  110. package/src/providers/model-rename-startup.ts +21 -1
  111. package/src/providers/qoder-models.ts +0 -1
  112. package/src/providers/quota-key-accounts.ts +50 -0
  113. package/src/providers/quota-routing-cache.ts +65 -6
  114. package/src/providers/quota-types.ts +7 -0
  115. package/src/providers/quota.ts +113 -30
  116. package/src/providers/registry.ts +237 -77
  117. package/src/providers/stale-context-window-migration.ts +92 -0
  118. package/src/providers/zai-responses-migration.ts +45 -0
  119. package/src/quota/reset-observer.ts +2 -1
  120. package/src/quota/reset-seen-store.ts +9 -1
  121. package/src/remote-control/crypto.ts +442 -0
  122. package/src/remote-control/host.ts +175 -0
  123. package/src/remote-control/index.ts +100 -0
  124. package/src/remote-control/protocol.ts +200 -0
  125. package/src/remote-control/relay.ts +162 -0
  126. package/src/remote-control/workspace-agent-protocol.ts +246 -0
  127. package/src/remote-control/workspace-rpc-framing.ts +128 -0
  128. package/src/remote-control/workspace-tools.ts +237 -0
  129. package/src/remote-control/workspace-utf8.ts +24 -0
  130. package/src/responses/custom-tool-compat.ts +23 -0
  131. package/src/router.ts +6 -1
  132. package/src/routing/compatibility/behavior.ts +3 -0
  133. package/src/server/adapter-resolve.ts +6 -2
  134. package/src/server/auth-cors.ts +71 -6
  135. package/src/server/chat-completions.ts +5 -3
  136. package/src/server/chat-native.ts +9 -0
  137. package/src/server/claude-messages.ts +25 -30
  138. package/src/server/context-history.ts +207 -0
  139. package/src/server/images.ts +28 -1
  140. package/src/server/index.ts +42 -22
  141. package/src/server/live.ts +2 -1
  142. package/src/server/management/agent-settings-routes.ts +7 -1
  143. package/src/server/management/config-routes.ts +3 -3
  144. package/src/server/management/grok-coupon-routes.ts +287 -0
  145. package/src/server/management/integration-routes.ts +6 -1
  146. package/src/server/management/oauth-account-routes.ts +124 -7
  147. package/src/server/management/provider-routes.ts +77 -2
  148. package/src/server/management/route-registry.ts +6 -1
  149. package/src/server/management-api.ts +7 -0
  150. package/src/server/request-log.ts +4 -0
  151. package/src/server/responses/codex-ws-exchange.ts +90 -14
  152. package/src/server/responses/codex-ws-wire.ts +51 -1
  153. package/src/server/responses/compact.ts +8 -1
  154. package/src/server/responses/core.ts +188 -22
  155. package/src/server/responses/ws-upstream.ts +1 -1
  156. package/src/server/responses-undeclared-tool-guard.ts +261 -12
  157. package/src/server/zai-responses-startup.ts +21 -0
  158. package/src/types/config.ts +33 -3
  159. package/src/types/provider.ts +54 -4
  160. package/src/types/request.ts +1 -1
  161. package/src/types/tools.ts +36 -6
  162. package/src/update/job.ts +33 -11
  163. package/src/vision/plan.ts +40 -37
  164. package/src/web-search/index.ts +1 -0
  165. package/gui/dist/assets/index-BoBRSehJ.css +0 -1
  166. 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
+ }