import type { ExtensionAPI, ProviderModelConfig } from "@earendil-works/pi-coding-agent"; import type { Api, Model, OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai"; 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"; const PLACEHOLDER_BASE_URL = "https://server.codeium.com"; let _pi: ExtensionAPI | null = null; function registerDevinProvider(pi: ExtensionAPI, models: ProviderModelConfig[]): void { pi.registerProvider(PROVIDER_ID, { name: "Devin Local", api: "devin-local", baseUrl: PLACEHOLDER_BASE_URL, models, oauth: { name: "Devin CLI", async login(_callbacks: OAuthLoginCallbacks): Promise { const creds = await loginWithCli(); if (_pi) { try { const catalog = await loadCliCatalog(); registerDevinProvider(_pi, modelsFromCatalog(catalog)); } catch { // keep current models } } return { refresh: "", access: creds.apiKey, expires: Date.now() + 365 * 24 * 60 * 60 * 1000, }; }, async refreshToken(credentials: OAuthCredentials): Promise { const creds = readCredentials(); if (!creds) return credentials; return { refresh: "", access: creds.apiKey, expires: Date.now() + 365 * 24 * 60 * 60 * 1000, }; }, getApiKey(credentials: OAuthCredentials): string { return readCredentials()?.apiKey || credentials.access; }, modifyModels(models: Model[], _credentials: OAuthCredentials): Model[] { return models; }, }, streamSimple: streamDevin, }); } export default async function (pi: ExtensionAPI): Promise { _pi = pi; registerDevinProvider(pi, FALLBACK_MODELS); try { if (readCredentials()) { const catalog = await loadCliCatalog(); registerDevinProvider(pi, modelsFromCatalog(catalog)); } } catch { // fallback models already registered } pi.on("session_start", async () => { try { if (!_pi || !readCredentials()) return; const catalog = await loadCliCatalog(); registerDevinProvider(_pi, modelsFromCatalog(catalog)); } catch { // keep current models } }); pi.registerCommand("devin-status", { description: "Show Devin CLI auth + binary status", handler: async (_args, ctx) => { const bin = await whichDevin(); const version = await devinVersion(); const status = await authStatus(); ctx.ui.notify( [ bin ? `CLI: ${bin}` : "CLI: not found", 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", ); }, }); pi.registerCommand("devin-refresh", { description: "Refresh Devin Local model catalog from `devin models list`", handler: async (_args, ctx) => { try { const catalog = await loadCliCatalog(); const models = modelsFromCatalog(catalog); registerDevinProvider(pi, models); ctx.ui.notify(`Devin: loaded ${models.length} families from the local CLI.`, "info"); } catch (error) { ctx.ui.notify( `Devin refresh failed: ${error instanceof Error ? error.message : String(error)}`, "error", ); } }, }); pi.on("session_shutdown", async () => { _pi = null; }); }