@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,65 @@
1
+ /*
2
+ * Derived from rsvedant/opencode-windsurf-auth (src/cloud-direct/), MIT licensed.
3
+ *
4
+ * MIT License
5
+ * Copyright (c) 2026 Vedant
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in all
15
+ * copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ * SOFTWARE.
24
+ */
25
+ /**
26
+ * Public surface of the cloud-direct module.
27
+ *
28
+ * Usage:
29
+ * import { streamChat } from './cloud-direct/index.js';
30
+ *
31
+ * for await (const delta of streamChat({
32
+ * apiKey: creds.apiKey,
33
+ * apiServerUrl: creds.apiServerUrl,
34
+ * modelUid: 'swe-1-6',
35
+ * messages: [{ role: 'user', content: 'hi' }],
36
+ * })) {
37
+ * process.stdout.write(delta);
38
+ * }
39
+ */
40
+
41
+ export {
42
+ streamChat,
43
+ streamChatEvents,
44
+ allocateCascadeId,
45
+ CloudChatError,
46
+ type CloudChatRequest,
47
+ type ChatHistoryItem,
48
+ type CloudChatEvent,
49
+ type ToolDef,
50
+ } from './chat.js';
51
+
52
+ export {
53
+ mintUserJwt,
54
+ getCachedUserJwt,
55
+ clearCachedUserJwt,
56
+ CloudAuthError,
57
+ } from './auth.js';
58
+
59
+ export {
60
+ getCachedCatalog,
61
+ clearCachedCatalog,
62
+ ModelNotAvailableError,
63
+ type ModelCatalogEntry,
64
+ type CacheEntry,
65
+ } from './catalog.js';
@@ -0,0 +1,134 @@
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
+ * `exa.codeium_common_pb.Metadata` proto builder.
7
+ *
8
+ * Field numbers come from src/plugin/discovery.ts (which reads the bundled
9
+ * extension.js for live numbers). For cloud-direct we hard-code the canonical
10
+ * set of fields the LS always populates — the IDE-extracted dynamic numbers
11
+ * would help if Windsurf renumbers, but we don't have a way to refresh those
12
+ * without the bundled extension.js path being present.
13
+ *
14
+ * Captured from real LS upstream traffic via mitm reverse-proxy. See
15
+ * docs/CLOUD_DIRECT.md → "The exact captured request body (annotated)".
16
+ */
17
+
18
+ import {
19
+ encodeMessage,
20
+ encodeString,
21
+ encodeTimestampBody,
22
+ encodeVarintField,
23
+ } from './wire.js';
24
+ import { randomBytes } from 'node:crypto';
25
+
26
+ /**
27
+ * extension_version + ide_version sent to the cloud. It MUST be a string the
28
+ * cloud recognizes as a real client release: an unknown version comes back as
29
+ * an opaque "an internal error occurred", with no hint that the version is what
30
+ * it objected to.
31
+ *
32
+ * Pinned to the version the shipped desktop client reports
33
+ * (`product.json` -> `windsurfVersion`) rather than to anything of ours. The
34
+ * previous pin of "2.0.0" predates the Devin rebrand and no longer chats.
35
+ * `OPENCODEX_DEVIN_CLIENT_VERSION` overrides it, which is the escape hatch when
36
+ * Cognition retires a version before this constant is updated.
37
+ */
38
+ const WINDSURF_VERSION_STRING = process.env.OPENCODEX_DEVIN_CLIENT_VERSION?.trim() || '3.9.19';
39
+
40
+ /**
41
+ * Identity the hosted chat RPC expects, which is not the desktop client's.
42
+ * GetChatMessage is calibrated against a different client name and version, and
43
+ * sending the IDE's own strings is one of the ways the request comes back as an
44
+ * opaque "an internal error occurred".
45
+ */
46
+ const CLOUD_CHAT_CLIENT_NAME = 'chisel';
47
+ const CLOUD_CHAT_CLIENT_VERSION = process.env.OPENCODEX_DEVIN_CHAT_CLIENT_VERSION?.trim() || '2026.8.18';
48
+ const CLOUD_CHAT_OS = 'windows';
49
+
50
+ /**
51
+ * Metadata #31 is a device fingerprint, and the server checks its shape rather
52
+ * than its value: 732 hex characters (366 bytes). Anything shorter — including
53
+ * absent — is rejected with the same opaque internal error, and a fresh random
54
+ * value per request is accepted, so nothing here identifies the machine.
55
+ */
56
+ const DEVICE_FINGERPRINT_BYTES = 366;
57
+
58
+ export interface MetadataInput {
59
+ /** Persistent api_key from OAuth (`devin-session-token$<JWT>`). */
60
+ apiKey: string;
61
+ /**
62
+ * Fresh user_jwt from GetUserJwt. The catalog RPC uses it; the hosted chat
63
+ * path does not need it and only sends it when an operator opts in.
64
+ */
65
+ userJwt?: string;
66
+ /** UUID — one per opencode session is fine. */
67
+ sessionId: string;
68
+ /** Monotonic, milliseconds since epoch. */
69
+ requestId: bigint;
70
+ /** UUID — one per RPC call. */
71
+ triggerId: string;
72
+ /** Optional override for the version string. Cosmetic. */
73
+ windsurfVersion?: string;
74
+ /** Optional override for the host OS string. */
75
+ osName?: string;
76
+ /**
77
+ * Emit the exact field set the hosted chat RPC accepts.
78
+ *
79
+ * GetChatMessage validates this message far more strictly than
80
+ * GetCascadeModelConfigs does, which is why the catalog has always worked
81
+ * while chat did not. The shape is seven identity fields, the optional
82
+ * user_jwt, and the fingerprint — the telemetry fields this module otherwise
83
+ * sends (request_id, session_id, ls_timestamp, trigger_id, plan_name,
84
+ * ide_type) are not part of it.
85
+ */
86
+ cloudChatShape?: boolean;
87
+ /** Override for Metadata #31; a random fingerprint is generated when absent. */
88
+ deviceHex?: string;
89
+ }
90
+
91
+ function osString(): string {
92
+ switch (process.platform) {
93
+ case 'darwin': return 'darwin';
94
+ case 'linux': return 'linux';
95
+ case 'win32': return 'windows';
96
+ default: return String(process.platform);
97
+ }
98
+ }
99
+
100
+ export function buildMetadata(input: MetadataInput): Buffer {
101
+ const version = input.windsurfVersion ?? WINDSURF_VERSION_STRING;
102
+ const os = input.osName ?? osString();
103
+ if (input.cloudChatShape) {
104
+ const clientVersion = input.windsurfVersion ?? CLOUD_CHAT_CLIENT_VERSION;
105
+ return Buffer.concat([
106
+ encodeString(1, CLOUD_CHAT_CLIENT_NAME),
107
+ encodeString(2, clientVersion),
108
+ encodeString(3, input.apiKey),
109
+ encodeString(4, 'en'),
110
+ encodeString(5, input.osName ?? CLOUD_CHAT_OS),
111
+ encodeString(7, clientVersion),
112
+ encodeString(12, CLOUD_CHAT_CLIENT_NAME),
113
+ ...(input.userJwt ? [encodeString(21, input.userJwt)] : []),
114
+ encodeString(31, input.deviceHex ?? randomBytes(DEVICE_FINGERPRINT_BYTES).toString('hex')),
115
+ ]);
116
+ }
117
+ const parts: Buffer[] = [
118
+ encodeString(1, 'windsurf'), // ide_name
119
+ encodeString(2, version), // extension_version
120
+ encodeString(3, input.apiKey), // api_key
121
+ encodeString(4, 'en'), // locale
122
+ encodeString(5, os), // os
123
+ encodeString(7, version), // ide_version
124
+ encodeVarintField(9, input.requestId), // request_id (uint64 monotonic)
125
+ encodeString(10, input.sessionId), // session_id
126
+ encodeString(12, 'windsurf'), // extension_name
127
+ encodeMessage(16, encodeTimestampBody()), // ls_timestamp (google.protobuf.Timestamp)
128
+ encodeString(25, input.triggerId), // trigger_id
129
+ encodeString(26, 'Unset'), // plan_name
130
+ encodeString(28, 'windsurf'), // ide_type
131
+ ];
132
+ if (input.userJwt) parts.push(encodeString(21, input.userJwt)); // user_jwt
133
+ return Buffer.concat(parts);
134
+ }
@@ -0,0 +1,206 @@
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
+ * Manual protobuf + Connect-RPC streaming envelope helpers.
7
+ *
8
+ * Connect-RPC streaming wire format (HTTPS POST body):
9
+ * ┌─────────────┬────────────────┬──────────────┐
10
+ * │ flags 1byte │ length 4B BE │ payload │
11
+ * └─────────────┴────────────────┴──────────────┘
12
+ * flags bit 0x01 = payload is gzip-compressed
13
+ * flags bit 0x02 = end-of-stream (trailer frame — JSON {error} or empty {})
14
+ *
15
+ * All `Get*` methods on `exa.api_server_pb.ApiServerService` that the
16
+ * language_server calls upstream use this format, content-type
17
+ * `application/connect+proto`, with `Connect-Protocol-Version: 1`.
18
+ *
19
+ * Kept tiny and dependency-free — same philosophy as src/plugin/protobuf.ts.
20
+ */
21
+
22
+ import * as zlib from 'zlib';
23
+
24
+ // ----------------------------------------------------------------------------
25
+ // Proto wire encode
26
+ // ----------------------------------------------------------------------------
27
+
28
+ export function encodeVarint(value: number | bigint): Buffer {
29
+ const v0 = BigInt(value);
30
+ // Reject negatives at the boundary. Proto3 spec encodes signed types as
31
+ // 10-byte sign-extended varints; we don't support that here and the
32
+ // current call sites never need it (tags, lengths, request ids — all
33
+ // strictly positive). The old loop body would have terminated with
34
+ // `Number(-1n)` = -1, producing a malformed single 0xFF byte that the
35
+ // server would misparse silently. Throw instead so future regressions
36
+ // surface immediately.
37
+ if (v0 < 0n) {
38
+ throw new RangeError(`encodeVarint: negative input not supported (got ${value})`);
39
+ }
40
+ const bytes: number[] = [];
41
+ let v = v0;
42
+ while (v > 127n) {
43
+ bytes.push(Number(v & 0x7fn) | 0x80);
44
+ v >>= 7n;
45
+ }
46
+ bytes.push(Number(v));
47
+ return Buffer.from(bytes);
48
+ }
49
+
50
+ export function encodeTag(fieldNum: number, wire: number): Buffer {
51
+ return encodeVarint((fieldNum << 3) | wire);
52
+ }
53
+
54
+ export function encodeString(fieldNum: number, s: string): Buffer {
55
+ const buf = Buffer.from(s, 'utf8');
56
+ return Buffer.concat([encodeTag(fieldNum, 2), encodeVarint(buf.length), buf]);
57
+ }
58
+
59
+ export function encodeMessage(fieldNum: number, body: Buffer): Buffer {
60
+ return Buffer.concat([encodeTag(fieldNum, 2), encodeVarint(body.length), body]);
61
+ }
62
+
63
+ export function encodeVarintField(fieldNum: number, v: number | bigint): Buffer {
64
+ return Buffer.concat([encodeTag(fieldNum, 0), encodeVarint(v)]);
65
+ }
66
+
67
+ export function encodeFixed64Field(fieldNum: number, v: number): Buffer {
68
+ const b = Buffer.alloc(8);
69
+ b.writeDoubleLE(v, 0);
70
+ return Buffer.concat([encodeTag(fieldNum, 1), b]);
71
+ }
72
+
73
+ export function encodeTimestampBody(): Buffer {
74
+ const now = Date.now();
75
+ const seconds = Math.floor(now / 1000);
76
+ const nanos = (now % 1000) * 1_000_000;
77
+ return Buffer.concat([
78
+ encodeVarintField(1, seconds),
79
+ nanos > 0 ? encodeVarintField(2, nanos) : Buffer.alloc(0),
80
+ ]);
81
+ }
82
+
83
+ // ----------------------------------------------------------------------------
84
+ // Proto wire decode
85
+ // ----------------------------------------------------------------------------
86
+
87
+ export function decodeVarint(buf: Buffer, offset: number): [bigint, number] {
88
+ let res = 0n;
89
+ let shift = 0n;
90
+ let i = offset;
91
+ while (i < buf.length) {
92
+ const b = buf[i++];
93
+ res |= BigInt(b & 0x7f) << shift;
94
+ if (!(b & 0x80)) return [res, i];
95
+ shift += 7n;
96
+ }
97
+ throw new Error('truncated varint');
98
+ }
99
+
100
+ export interface ProtoField {
101
+ num: number;
102
+ wire: number;
103
+ /** varint → bigint, fixed → 8/4 byte Buffer, length-delim → payload Buffer. */
104
+ value: bigint | Buffer;
105
+ }
106
+
107
+ export function* iterFields(buf: Buffer): Generator<ProtoField> {
108
+ let i = 0;
109
+ while (i < buf.length) {
110
+ const [tagBig, ai] = decodeVarint(buf, i);
111
+ i = ai;
112
+ const tag = Number(tagBig);
113
+ const num = tag >> 3;
114
+ const wire = tag & 0x7;
115
+ if (wire === 0) {
116
+ const [v, bi] = decodeVarint(buf, i);
117
+ i = bi;
118
+ yield { num, wire, value: v };
119
+ } else if (wire === 1) {
120
+ // Bounds-check: a truncated frame mustn't yield a short fixed64 slice
121
+ // that downstream readers treat as a full 8-byte value. Stop iterating
122
+ // cleanly instead.
123
+ if (i + 8 > buf.length) return;
124
+ yield { num, wire, value: buf.slice(i, i + 8) };
125
+ i += 8;
126
+ } else if (wire === 2) {
127
+ const [n, ci] = decodeVarint(buf, i);
128
+ i = ci;
129
+ const len = Number(n);
130
+ // Bounds-check: when the declared length runs past the buffer, the
131
+ // frame is corrupt or truncated. Returning short-buffered slices to
132
+ // downstream parsers used to misparse silently (M12).
133
+ if (len < 0 || i + len > buf.length) return;
134
+ yield { num, wire, value: buf.slice(i, i + len) };
135
+ i += len;
136
+ } else if (wire === 5) {
137
+ if (i + 4 > buf.length) return;
138
+ yield { num, wire, value: buf.slice(i, i + 4) };
139
+ i += 4;
140
+ } else if (wire === 3 || wire === 4) {
141
+ // Wire types 3 (start group) and 4 (end group) are deprecated in
142
+ // proto3 but show up in some Codeium server-generated messages. They
143
+ // carry no length info; the safe behavior is to stop iterating
144
+ // gracefully rather than tear down the whole frame parse.
145
+ return;
146
+ } else {
147
+ // Unknown wire type — bail rather than misalign.
148
+ return;
149
+ }
150
+ }
151
+ }
152
+
153
+ // ----------------------------------------------------------------------------
154
+ // Connect-streaming envelope
155
+ // ----------------------------------------------------------------------------
156
+
157
+ /**
158
+ * Wrap `body` (a serialized proto message) in a Connect-streaming envelope.
159
+ * If `compress` is true, gzip the payload and set the 0x01 flag.
160
+ */
161
+ export function frameConnectStream(body: Buffer, compress = true): Buffer {
162
+ let payload = body;
163
+ let flags = 0;
164
+ if (compress) {
165
+ payload = zlib.gzipSync(body);
166
+ flags |= 0x01;
167
+ }
168
+ const header = Buffer.alloc(5);
169
+ header[0] = flags;
170
+ header.writeUInt32BE(payload.length, 1);
171
+ return Buffer.concat([header, payload]);
172
+ }
173
+
174
+ export interface ConnectFrame {
175
+ flags: number;
176
+ /** Decompressed payload (gzip handled here if flags & 0x01). */
177
+ payload: Buffer;
178
+ /** Frame is the trailer (end-of-stream). */
179
+ eos: boolean;
180
+ }
181
+
182
+ /**
183
+ * Parse all Connect-streaming frames out of a response body.
184
+ *
185
+ * Returns array of decoded frames. Each frame's payload is already gzip-decoded
186
+ * if the compression flag was set.
187
+ */
188
+ export function parseConnectFrames(buf: Buffer): ConnectFrame[] {
189
+ const out: ConnectFrame[] = [];
190
+ let i = 0;
191
+ while (i + 5 <= buf.length) {
192
+ const flags = buf[i];
193
+ const len = buf.readUInt32BE(i + 1);
194
+ if (i + 5 + len > buf.length) break;
195
+ let payload = buf.slice(i + 5, i + 5 + len);
196
+ if (flags & 0x01) {
197
+ // Compressed frame. If gunzip fails the frame is genuinely corrupt
198
+ // — surfacing as a thrown error beats parsing raw gzip bytes as proto
199
+ // (which previously produced misleading "yielded bad wire type" downstream).
200
+ payload = zlib.gunzipSync(payload);
201
+ }
202
+ out.push({ flags, payload, eos: (flags & 0x02) !== 0 });
203
+ i += 5 + len;
204
+ }
205
+ return out;
206
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Live Devin / Cognition model discovery via GetCascadeModelConfigs.
3
+ *
4
+ * The live catalog is the source of truth for the model roster. The endpoint
5
+ * returns effort-suffixed variants (e.g. `gpt-5-6-sol-high`); we collapse those
6
+ * to base ids so the picker stays clean and the adapter appends the effort
7
+ * suffix at request time. `DEVIN_STATIC_MODELS` is only a degraded-mode
8
+ * fallback for when there is no API key or discovery fails.
9
+ */
10
+ import { getCachedCatalog, type ModelCatalogEntry } from "./cloud-direct";
11
+
12
+ const DEFAULT_HOST = "https://server.codeium.com";
13
+
14
+ /**
15
+ * Degraded-mode fallback shown when there is no API key or live discovery
16
+ * fails. The live catalog overrides this whenever discovery succeeds.
17
+ */
18
+ export const DEVIN_STATIC_MODELS = [
19
+ "swe-1-7",
20
+ "swe-1-7-lightning",
21
+ "gpt-5-6-sol",
22
+ "gpt-5-6-luna",
23
+ "gpt-5-6-terra",
24
+ "claude-opus-4-8",
25
+ "claude-fable-5-1",
26
+ "claude-sonnet-5",
27
+ "glm-5-2",
28
+ "kimi-k2-7",
29
+ "grok-4-5",
30
+ ] as const;
31
+
32
+ /**
33
+ * Degraded-mode context windows, used only when live discovery cannot run.
34
+ *
35
+ * Every number here was read from a live `GetCascadeModelConfigs` response
36
+ * (`ClientModelConfig` field #18) rather than from documentation, because
37
+ * Cognition publishes none: the Devin CLI and Desktop model pages, the SWE-2
38
+ * and SWE-1.7 announcements, and the Windsurf model reference all state model
39
+ * names without a context window. The only published numbers are long-context
40
+ * pricing thresholds, which are a different quantity and were not used.
41
+ *
42
+ * The previous copy of this table was wrong for nine of its eleven rows — the
43
+ * three Claude models were listed at 200k against an actual 1M, `grok-4-5` at
44
+ * 256k against 500k, and the GPT rows at 1.05M against 1M — because it was
45
+ * assembled from each model's upstream vendor window instead of what Cognition
46
+ * actually serves. Measure the catalog when updating this; do not carry a
47
+ * number over from the model's original vendor.
48
+ */
49
+ export const DEVIN_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
50
+ "swe-2": 262_000,
51
+ "swe-1-7": 262_000,
52
+ "swe-1-7-lightning": 202_752,
53
+ "swe-1-6": 200_000,
54
+ "gpt-5-6-sol": 1_000_000,
55
+ "gpt-5-6-luna": 1_000_000,
56
+ "gpt-5-6-terra": 1_000_000,
57
+ "gpt-6-astra": 1_000_000,
58
+ "claude-opus-4-8": 1_000_000,
59
+ "claude-opus-5": 1_000_000,
60
+ "claude-fable-5-1": 1_000_000,
61
+ "claude-sonnet-5": 1_000_000,
62
+ "glm-5-2": 200_000,
63
+ "glm-5-3": 1_048_576,
64
+ "kimi-k2-7": 262_144,
65
+ "kimi-k3": 1_048_576,
66
+ "gemini-3-8-flash": 1_048_576,
67
+ "grok-4-5": 500_000,
68
+ "grok-4-6": 500_000,
69
+ };
70
+
71
+ /**
72
+ * Trailing tokens that the Cognition catalog appends as effort/variant
73
+ * suffixes. Stripped to collapse suffixed UIDs to their base id.
74
+ */
75
+ const EFFORT_TOKENS = new Set([
76
+ "low", "medium", "high", "xhigh", "max", "none", "fast", "priority", "1m",
77
+ ]);
78
+
79
+ /** Collapse an effort-suffixed UID to its base id (e.g. `gpt-5-6-sol-high` → `gpt-5-6-sol`). */
80
+ export function collapseDevinModelUid(uid: string): string {
81
+ const parts = uid.split("-");
82
+ while (parts.length > 1 && EFFORT_TOKENS.has(parts[parts.length - 1]!)) {
83
+ parts.pop();
84
+ }
85
+ return parts.join("-");
86
+ }
87
+
88
+ export type DevinUsableModelsResult =
89
+ | { ok: true; models: string[]; contextWindows: Record<string, number> }
90
+ | { ok: false; error: "auth" | "http" | "empty" | "unknown"; detail?: string };
91
+
92
+ /**
93
+ * Fetch the live model roster from Cognition's `GetCascadeModelConfigs` and
94
+ * collapse effort-suffixed variants to base ids. The returned list is the
95
+ * authoritative model roster for the signed-in account.
96
+ */
97
+ export async function fetchDevinUsableModels(opts: {
98
+ apiKey: string;
99
+ baseUrl?: string;
100
+ signal?: AbortSignal;
101
+ }): Promise<DevinUsableModelsResult> {
102
+ try {
103
+ const host = (opts.baseUrl || DEFAULT_HOST).replace(/\/$/, "");
104
+ const catalog = await getCachedCatalog(opts.apiKey, host, opts.signal);
105
+ if (!catalog) return { ok: false, error: "empty" };
106
+ const bases = new Set<string>();
107
+ const contextWindows: Record<string, number> = {};
108
+ for (const entry of catalog.byUid.values()) {
109
+ if (entry.disabled) continue;
110
+ // Skip internal enum constants (e.g. MODEL_GPT_5_2_LOW, MODEL_PRIVATE_*).
111
+ // Real chat model UIDs are lowercase dashed strings (swe-1-7, gpt-5-6-sol).
112
+ if (entry.modelUid.startsWith("MODEL_")) continue;
113
+ const base = collapseDevinModelUid(entry.modelUid);
114
+ bases.add(base);
115
+ if (entry.contextWindow && entry.contextWindow > 0) {
116
+ // Variants of one base can disagree: the opt-in `-1m` rows report a
117
+ // larger window than the plain row of the same base, and both collapse
118
+ // here because `1m` is an effort token. Keep the smallest, because the
119
+ // base id routes to the plain variant — advertising the long-context
120
+ // number would promise a window the request the picker actually sends
121
+ // cannot use.
122
+ const seen = contextWindows[base];
123
+ contextWindows[base] = seen === undefined ? entry.contextWindow : Math.min(seen, entry.contextWindow);
124
+ }
125
+ }
126
+ if (bases.size === 0) return { ok: false, error: "empty" };
127
+ return { ok: true, models: [...bases].sort(), contextWindows };
128
+ } catch (error) {
129
+ const message = error instanceof Error ? error.message : String(error);
130
+ if (/unauth|401|invalid token|login/i.test(message)) return { ok: false, error: "auth", detail: message };
131
+ return { ok: false, error: "unknown", detail: message };
132
+ }
133
+ }