diff --git a/extensions/index.ts b/extensions/index.ts index 6796c1f..ac6ae16 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -3,6 +3,7 @@ import type { Api, Model, OAuthCredentials, OAuthLoginCallbacks } from "@earendi import { authStatus, loginWithCli, readCredentials } from "../src/credentials.js"; import { whichDevin, devinVersion } from "../src/cli.js"; import { FALLBACK_MODELS, loadCliCatalog, modelsFromCatalog } from "../src/models.js"; +import { CLIENT_IDE, CLIENT_VERSION } from "../src/metadata.js"; import { streamDevin } from "../src/stream.js"; const PROVIDER_ID = "devin"; @@ -86,7 +87,8 @@ export default async function (pi: ExtensionAPI): Promise { ctx.ui.notify( [ bin ? `CLI: ${bin}` : "CLI: not found", - version ? `Version: ${version}` : "Version: unknown", + version ? `CLI version: ${version}` : "CLI version: unknown", + `Client identity: ${CLIENT_IDE} ${CLIENT_VERSION}`, status.loggedIn ? "Auth: signed in via Devin CLI" : "Auth: not signed in. Run /login devin or `devin auth login`", ].join("\n"), status.loggedIn && bin ? "info" : "warning", diff --git a/package.json b/package.json index 6555202..7e9baf2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-devin", - "version": "0.1.0", + "version": "0.1.1", "description": "Pi provider for Devin Local models. Auth and catalog come from the local Devin CLI; Pi stays the harness.", "type": "module", "keywords": [ diff --git a/src/metadata.ts b/src/metadata.ts index 5a27b51..8809b3a 100644 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -1,3 +1,4 @@ +import { existsSync, readFileSync } from "node:fs"; import { encodeMessage, encodeString, @@ -5,7 +6,30 @@ import { encodeVarintField, } from "./wire.js"; -const DEFAULT_VERSION = "3000.4.25"; +/** + * Cognition version-gates GetChatMessage against a known Windsurf/Devin Desktop + * release. The Devin CLI build id (`3000.4.25`) is not that string and the + * server answers: "Your Windsurf version is out of date." + * Prefer the installed Devin.app product.json, then a current desktop release. + */ +const FALLBACK_WINDSURF_VERSION = "3.6.27"; +const PRODUCT_JSON = + "/Applications/Devin.app/Contents/Resources/app/product.json"; + +function desktopWindsurfVersion(): string { + try { + if (!existsSync(PRODUCT_JSON)) return FALLBACK_WINDSURF_VERSION; + const product = JSON.parse(readFileSync(PRODUCT_JSON, "utf8")) as { + windsurfVersion?: string; + }; + return product.windsurfVersion || FALLBACK_WINDSURF_VERSION; + } catch { + return FALLBACK_WINDSURF_VERSION; + } +} + +export const CLIENT_VERSION = desktopWindsurfVersion(); +export const CLIENT_IDE = "windsurf"; export interface MetadataInput { apiKey: string; @@ -14,10 +38,12 @@ export interface MetadataInput { requestId: bigint; triggerId: string; version?: string; + ide?: string; } export function buildMetadata(input: MetadataInput): Buffer { - const version = input.version ?? DEFAULT_VERSION; + const version = input.version ?? CLIENT_VERSION; + const ide = input.ide ?? CLIENT_IDE; const os = process.platform === "darwin" ? "darwin" @@ -25,7 +51,7 @@ export function buildMetadata(input: MetadataInput): Buffer { ? "windows" : "linux"; const parts: Buffer[] = [ - encodeString(1, "devin"), + encodeString(1, ide), encodeString(2, version), encodeString(3, input.apiKey), encodeString(4, "en"), @@ -33,11 +59,11 @@ export function buildMetadata(input: MetadataInput): Buffer { encodeString(7, version), encodeVarintField(9, input.requestId), encodeString(10, input.sessionId), - encodeString(12, "devin"), + encodeString(12, ide), encodeMessage(16, encodeTimestampBody()), encodeString(25, input.triggerId), encodeString(26, "Unset"), - encodeString(28, "devin"), + encodeString(28, ide), ]; if (input.userJwt) parts.push(encodeString(21, input.userJwt)); return Buffer.concat(parts);