@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,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
|
+
}
|