Initial pi-devin package: Devin Local models inside Pi.
Auth and catalog come from the local Devin CLI. Pi stays the harness.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
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 { 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<OAuthCredentials> {
|
||||
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<OAuthCredentials> {
|
||||
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<Api>[], _credentials: OAuthCredentials): Model<Api>[] {
|
||||
return models;
|
||||
},
|
||||
},
|
||||
streamSimple: streamDevin,
|
||||
});
|
||||
}
|
||||
|
||||
export default async function (pi: ExtensionAPI): Promise<void> {
|
||||
_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 ? `Version: ${version}` : "Version: unknown",
|
||||
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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user