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:
Kashyab Ambarani
2026-08-16 20:29:56 -07:00
commit d36c0b3705
15 changed files with 1658 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
import { existsSync, readdirSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { execFile, spawn } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const ZED_DEVIN =
join(
homedir(),
"Library/Application Support/Zed/external_agents/registry/devin",
);
const KNOWN_BINS = [
process.env.DEVIN_CLI,
join(homedir(), ".local/bin/devin"),
join(homedir(), ".devin/bin/devin"),
"/opt/homebrew/bin/devin",
"/usr/local/bin/devin",
"/Applications/Devin.app/Contents/Resources/app/extensions/windsurf/devin/bin/devin",
].filter((p): p is string => Boolean(p));
let cachedBin: string | null | undefined;
function newestZedDevin(): string | null {
try {
if (!existsSync(ZED_DEVIN)) return null;
const versions = readdirSync(ZED_DEVIN)
.map((name) => {
const bin = join(ZED_DEVIN, name, "bin/devin");
try {
return { bin, mtime: statSync(bin).mtimeMs };
} catch {
return null;
}
})
.filter((row): row is { bin: string; mtime: number } => Boolean(row))
.sort((a, b) => b.mtime - a.mtime);
return versions[0]?.bin ?? null;
} catch {
return null;
}
}
export function findDevinBin(): string | null {
if (cachedBin !== undefined) return cachedBin;
for (const bin of KNOWN_BINS) {
if (existsSync(bin)) {
cachedBin = bin;
return bin;
}
}
const zed = newestZedDevin();
if (zed) {
cachedBin = zed;
return zed;
}
cachedBin = null;
return null;
}
export function clearDevinBinCache(): void {
cachedBin = undefined;
}
export async function whichDevin(): Promise<string | null> {
const known = findDevinBin();
if (known) return known;
try {
const { stdout } = await execFileAsync("/usr/bin/which", ["devin"], { timeout: 5_000 });
const path = stdout.trim();
if (path && existsSync(path)) {
cachedBin = path;
return path;
}
} catch {
// not on PATH
}
return null;
}
export async function runDevin(
args: string[],
opts: { timeoutMs?: number; inheritStdio?: boolean } = {},
): Promise<{ stdout: string; stderr: string; code: number }> {
const bin = await whichDevin();
if (!bin) {
throw new Error(
"Devin CLI not found. Install Devin, or set DEVIN_CLI to the `devin` binary (Zed ACP registry and Devin.app are also checked).",
);
}
if (opts.inheritStdio) {
return await new Promise((resolve, reject) => {
const child = spawn(bin, args, { stdio: "inherit" });
child.on("error", reject);
child.on("close", (code) => {
resolve({ stdout: "", stderr: "", code: code ?? 1 });
});
});
}
try {
const { stdout, stderr } = await execFileAsync(bin, args, {
timeout: opts.timeoutMs ?? 30_000,
maxBuffer: 8 * 1024 * 1024,
});
return { stdout, stderr, code: 0 };
} catch (error) {
const err = error as { stdout?: string; stderr?: string; code?: number; message?: string };
if (typeof err.code === "number") {
return { stdout: err.stdout ?? "", stderr: err.stderr ?? "", code: err.code };
}
throw error;
}
}
export async function devinVersion(): Promise<string | null> {
try {
const { stdout, code } = await runDevin(["version"], { timeoutMs: 8_000 });
if (code !== 0) return null;
return stdout.trim() || null;
} catch {
return null;
}
}
+96
View File
@@ -0,0 +1,96 @@
import type { Context, Message, Tool } from "@earendil-works/pi-ai";
export interface ContentPart {
type: "text" | "image";
text?: string;
mimeType?: string;
base64Data?: string;
}
export interface ChatHistoryItem {
role: "user" | "assistant" | "system" | "tool";
content: string | ContentPart[];
tool_call_id?: string;
tool_calls?: Array<{ id: string; name: string; arguments: string }>;
}
export interface ToolDef {
name: string;
description: string;
parameters: unknown;
}
export interface MappedChat {
messages: ChatHistoryItem[];
tools: ToolDef[];
}
function userContent(content: Message["content"]): string | ContentPart[] {
if (typeof content === "string") return content;
if (!Array.isArray(content)) return "";
const parts: ContentPart[] = [];
for (const part of content) {
if (!part || typeof part !== "object") continue;
if (part.type === "text") parts.push({ type: "text", text: part.text });
if (part.type === "image") {
parts.push({ type: "image", mimeType: part.mimeType, base64Data: part.data });
}
}
return parts;
}
export function mapContextToChat(context: Context): MappedChat {
const messages: ChatHistoryItem[] = [];
if (context.systemPrompt) {
messages.push({ role: "system", content: context.systemPrompt });
}
for (const message of context.messages) {
if (message.role === "user") {
messages.push({ role: "user", content: userContent(message.content) });
continue;
}
if (message.role === "assistant") {
const texts: string[] = [];
const toolCalls: Array<{ id: string; name: string; arguments: string }> = [];
for (const part of message.content) {
if (part.type === "text") texts.push(part.text);
if (part.type === "toolCall") {
toolCalls.push({
id: part.id,
name: part.name,
arguments: JSON.stringify(part.arguments ?? {}),
});
}
}
messages.push({
role: "assistant",
content: texts.join("\n"),
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
});
continue;
}
if (message.role === "toolResult") {
const text =
typeof message.content === "string"
? message.content
: message.content
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("\n");
messages.push({
role: "tool",
content: text,
tool_call_id: message.toolCallId,
});
}
}
const tools: ToolDef[] = (context.tools ?? []).map((tool: Tool) => ({
name: tool.name,
description: tool.description,
parameters: tool.parameters,
}));
return { messages, tools };
}
+77
View File
@@ -0,0 +1,77 @@
import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { runDevin } from "./cli.js";
export interface DevinCredentials {
apiKey: string;
apiServerUrl: string;
webappHost: string;
apiUrl: string;
path: string;
}
const CREDENTIALS_PATH = join(homedir(), ".local/share/devin/credentials.toml");
function parseTomlStrings(text: string): Record<string, string> {
const out: Record<string, string> = {};
for (const line of text.split("\n")) {
const match = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*"(.*)"\s*$/);
if (match) out[match[1]] = match[2];
}
return out;
}
export function credentialsPath(): string {
return CREDENTIALS_PATH;
}
export function readCredentials(): DevinCredentials | null {
if (!existsSync(CREDENTIALS_PATH)) return null;
const raw = parseTomlStrings(readFileSync(CREDENTIALS_PATH, "utf8"));
const apiKey = raw.windsurf_api_key || raw.api_key;
if (!apiKey) return null;
return {
apiKey,
apiServerUrl: (raw.api_server_url || "https://server.codeium.com").replace(/\/$/, ""),
webappHost: raw.devin_webapp_host || "app.devin.ai",
apiUrl: raw.devin_api_url || "https://api.devin.ai",
path: CREDENTIALS_PATH,
};
}
export async function authStatus(): Promise<{
loggedIn: boolean;
summary: string;
}> {
const creds = readCredentials();
try {
const { stdout, stderr, code } = await runDevin(["auth", "status"], { timeoutMs: 15_000 });
const text = `${stdout}\n${stderr}`.trim();
const loggedIn = code === 0 && /logged in/i.test(text);
return { loggedIn: loggedIn || Boolean(creds), summary: text || (creds ? "credentials.toml present" : "not signed in") };
} catch (error) {
if (creds) {
return { loggedIn: true, summary: `Devin credentials present at ${CREDENTIALS_PATH}` };
}
return {
loggedIn: false,
summary: error instanceof Error ? error.message : String(error),
};
}
}
export async function loginWithCli(): Promise<DevinCredentials> {
const already = readCredentials();
const status = await authStatus();
if (already && status.loggedIn) return already;
const { code } = await runDevin(["auth", "login"], { inheritStdio: true });
const creds = readCredentials();
if (!creds) {
throw new Error(
`\`devin auth login\` ${code === 0 ? "finished" : `exited ${code}`} but ${CREDENTIALS_PATH} is missing. Run \`devin auth login\` yourself, then /login devin again.`,
);
}
return creds;
}
+103
View File
@@ -0,0 +1,103 @@
import { randomUUID } from "node:crypto";
import { buildMetadata } from "./metadata.js";
import { encodeMessage, iterFields } from "./wire.js";
export interface MintedUserJwt {
jwt: string;
expiresAt: number;
}
function anySignal(signals: AbortSignal[]): AbortSignal {
const builtin = (AbortSignal as unknown as { any?: (s: AbortSignal[]) => AbortSignal }).any;
if (typeof builtin === "function") return builtin(signals);
const controller = new AbortController();
const onAbort = (reason: unknown): void => {
if (!controller.signal.aborted) controller.abort(reason);
};
for (const signal of signals) {
if (signal.aborted) {
onAbort(signal.reason);
break;
}
signal.addEventListener("abort", () => onAbort(signal.reason), { once: true });
}
return controller.signal;
}
export async function mintUserJwt(
apiKey: string,
host: string,
signal?: AbortSignal,
): Promise<MintedUserJwt> {
const metadata = buildMetadata({
apiKey,
sessionId: randomUUID(),
requestId: BigInt(Date.now()),
triggerId: randomUUID(),
});
const timeout = AbortSignal.timeout(30_000);
const combined = signal ? anySignal([signal, timeout]) : timeout;
const resp = await fetch(`${host.replace(/\/$/, "")}/exa.auth_pb.AuthService/GetUserJwt`, {
method: "POST",
headers: {
"Content-Type": "application/proto",
"Connect-Protocol-Version": "1",
},
body: new Uint8Array(encodeMessage(1, metadata)),
signal: combined,
});
const buf = Buffer.from(await resp.arrayBuffer());
if (!resp.ok) {
throw new Error(`GetUserJwt HTTP ${resp.status}: ${buf.toString("utf8").slice(0, 240)}`);
}
let jwt: string | null = null;
for (const field of iterFields(buf)) {
if (field.num === 1 && field.wire === 2 && Buffer.isBuffer(field.value)) {
const value = field.value.toString("utf8");
if (value.startsWith("eyJ")) {
jwt = value;
break;
}
}
}
if (!jwt) throw new Error("GetUserJwt returned no JWT");
let expiresAt = Math.floor(Date.now() / 1000) + 600;
try {
const payload = jwt.split(".")[1] ?? "";
const padded = payload + "=".repeat((4 - (payload.length % 4)) % 4);
const parsed = JSON.parse(Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8"));
if (typeof parsed.exp === "number") expiresAt = parsed.exp;
} catch {
// keep fallback
}
return { jwt, expiresAt };
}
let cache: { jwt: string; expiresAt: number; apiKey: string; host: string } | null = null;
const inFlight = new Map<string, Promise<MintedUserJwt>>();
export async function getCachedUserJwt(apiKey: string, host: string, signal?: AbortSignal): Promise<string> {
const now = Math.floor(Date.now() / 1000);
if (cache && cache.apiKey === apiKey && cache.host === host && cache.expiresAt > now + 60) {
return cache.jwt;
}
const key = `${host}\x1f${apiKey}`;
const existing = inFlight.get(key);
if (existing) return (await existing).jwt;
const promise = mintUserJwt(apiKey, host, signal);
inFlight.set(key, promise);
try {
const minted = await promise;
cache = { jwt: minted.jwt, expiresAt: minted.expiresAt, apiKey, host };
return minted.jwt;
} finally {
inFlight.delete(key);
}
}
export function clearCachedUserJwt(): void {
cache = null;
inFlight.clear();
}
+44
View File
@@ -0,0 +1,44 @@
import {
encodeMessage,
encodeString,
encodeTimestampBody,
encodeVarintField,
} from "./wire.js";
const DEFAULT_VERSION = "3000.4.25";
export interface MetadataInput {
apiKey: string;
userJwt?: string;
sessionId: string;
requestId: bigint;
triggerId: string;
version?: string;
}
export function buildMetadata(input: MetadataInput): Buffer {
const version = input.version ?? DEFAULT_VERSION;
const os =
process.platform === "darwin"
? "darwin"
: process.platform === "win32"
? "windows"
: "linux";
const parts: Buffer[] = [
encodeString(1, "devin"),
encodeString(2, version),
encodeString(3, input.apiKey),
encodeString(4, "en"),
encodeString(5, os),
encodeString(7, version),
encodeVarintField(9, input.requestId),
encodeString(10, input.sessionId),
encodeString(12, "devin"),
encodeMessage(16, encodeTimestampBody()),
encodeString(25, input.triggerId),
encodeString(26, "Unset"),
encodeString(28, "devin"),
];
if (input.userJwt) parts.push(encodeString(21, input.userJwt));
return Buffer.concat(parts);
}
+223
View File
@@ -0,0 +1,223 @@
import type { ThinkingLevelMap } from "@earendil-works/pi-ai";
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
import { runDevin } from "./cli.js";
export interface DevinVariant {
model_uid: string;
label: string;
max_context_tokens?: number;
max_output_tokens?: number;
cost_tier?: string;
cost_summary?: string;
is_new?: boolean;
is_beta?: boolean;
}
export interface DevinFamily {
family_label: string;
family_uid: string;
slug: string;
aliases?: string[];
variants: DevinVariant[];
}
export interface DevinCatalog {
families: DevinFamily[];
}
const THINKING_ORDER = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
function parseCost(summary?: string): ProviderModelConfig["cost"] {
const empty = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
if (!summary) return empty;
const input = summary.match(/\$([0-9.]+)\s*\/\s*MTok In/i);
const output = summary.match(/\$([0-9.]+)\s*\/\s*MTok Out/i);
const inCost = input ? Number(input[1]) : 0;
const outCost = output ? Number(output[1]) : 0;
return {
input: inCost,
output: outCost,
cacheRead: Number((inCost * 0.1).toFixed(4)),
cacheWrite: Number((inCost * 1.25).toFixed(4)),
};
}
function variantKey(uid: string): string | null {
const suffixes = [
"none-priority",
"low-priority",
"medium-priority",
"high-priority",
"xhigh-priority",
"max-priority",
"low-fast",
"medium-fast",
"high-fast",
"xhigh-fast",
"max-fast",
"thinking-1m",
"thinking",
"none",
"low",
"medium",
"high",
"xhigh",
"max",
"minimal",
];
for (const suffix of suffixes) {
if (uid === suffix || uid.endsWith(`-${suffix}`)) return suffix;
}
return null;
}
function thinkingFromSuffix(suffix: string | null): keyof ThinkingLevelMap | null {
if (!suffix) return "high";
if (suffix === "none" || suffix === "none-priority") return "off";
if (suffix === "minimal") return "minimal";
if (suffix.startsWith("low")) return "low";
if (suffix.startsWith("medium")) return "medium";
if (suffix.startsWith("high") && !suffix.startsWith("xhigh")) return "high";
if (suffix.startsWith("xhigh")) return "xhigh";
if (suffix.startsWith("max")) return "max";
if (suffix.includes("thinking")) return "high";
return null;
}
function preferredDefault(map: ThinkingLevelMap): string | undefined {
for (const level of ["high", "medium", "max", "xhigh", "low", "minimal", "off"] as const) {
const value = map[level];
if (typeof value === "string") return value;
}
return undefined;
}
function familyToModels(family: DevinFamily): ProviderModelConfig[] {
const usable = family.variants.filter((variant) => {
const key = variantKey(variant.model_uid);
return !key || (!key.includes("priority") && !key.includes("fast") && key !== "thinking-1m");
});
const source = usable.length > 0 ? usable : family.variants;
const thinkingLevelMap: ThinkingLevelMap = {};
for (const variant of source) {
const level = thinkingFromSuffix(variantKey(variant.model_uid));
if (level && thinkingLevelMap[level] === undefined) {
thinkingLevelMap[level] = variant.model_uid;
}
}
const defaultUid = preferredDefault(thinkingLevelMap) ?? source[0]?.model_uid ?? family.family_uid;
const sample = source.find((variant) => variant.model_uid === defaultUid) ?? source[0];
if (!sample) return [];
const mappedLevels = THINKING_ORDER.filter((level) => typeof thinkingLevelMap[level] === "string");
const reasoning = mappedLevels.length > 1;
return [
{
id: defaultUid,
name: family.family_label || family.slug || defaultUid,
reasoning,
thinkingLevelMap: reasoning ? thinkingLevelMap : undefined,
input: ["text", "image"],
cost: parseCost(sample.cost_summary),
contextWindow: sample.max_context_tokens ?? 256_000,
maxTokens: sample.max_output_tokens ?? 128_000,
},
];
}
export const FALLBACK_MODELS: ProviderModelConfig[] = [
{
id: "claude-opus-5-high",
name: "Claude Opus 5",
reasoning: true,
thinkingLevelMap: {
low: "claude-opus-5-low",
medium: "claude-opus-5-medium",
high: "claude-opus-5-high",
xhigh: "claude-opus-5-xhigh",
max: "claude-opus-5-max",
},
input: ["text", "image"],
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
contextWindow: 1_000_000,
maxTokens: 128_000,
},
{
id: "claude-5-fable-high",
name: "Claude Fable 5",
reasoning: true,
thinkingLevelMap: {
low: "claude-5-fable-low",
medium: "claude-5-fable-medium",
high: "claude-5-fable-high",
xhigh: "claude-5-fable-xhigh",
max: "claude-5-fable-max",
},
input: ["text", "image"],
cost: { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
contextWindow: 1_000_000,
maxTokens: 128_000,
},
{
id: "gpt-5-6-sol-high",
name: "GPT-5.6 Sol",
reasoning: true,
thinkingLevelMap: {
off: "gpt-5-6-sol-none",
low: "gpt-5-6-sol-low",
medium: "gpt-5-6-sol-medium",
high: "gpt-5-6-sol-high",
xhigh: "gpt-5-6-sol-xhigh",
max: "gpt-5-6-sol-max",
},
input: ["text", "image"],
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
contextWindow: 1_000_000,
maxTokens: 128_000,
},
{
id: "swe-1-7",
name: "SWE-1.7",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 262_000,
maxTokens: 128_000,
},
];
export function modelsFromCatalog(catalog: DevinCatalog | null): ProviderModelConfig[] {
if (!catalog?.families?.length) return FALLBACK_MODELS;
const models = catalog.families.flatMap(familyToModels);
return models.length > 0 ? models : FALLBACK_MODELS;
}
export async function loadCliCatalog(): Promise<DevinCatalog | null> {
const { stdout, code, stderr } = await runDevin(["models", "list", "--format", "json"], {
timeoutMs: 20_000,
});
if (code !== 0) {
throw new Error(stderr.trim() || `devin models list exited ${code}`);
}
const parsed = JSON.parse(stdout) as DevinCatalog;
if (!parsed?.families) return null;
return parsed;
}
export function resolveModelUid(
modelId: string,
thinkingLevelMap: ThinkingLevelMap | undefined,
reasoning?: string,
): string {
if (reasoning && thinkingLevelMap) {
const mapped = thinkingLevelMap[reasoning as keyof ThinkingLevelMap];
if (typeof mapped === "string") return mapped;
}
if (thinkingLevelMap) {
const fallback = preferredDefault(thinkingLevelMap);
if (fallback) return fallback;
}
return modelId;
}
+568
View File
@@ -0,0 +1,568 @@
import { randomUUID } from "node:crypto";
import * as zlib from "node:zlib";
import {
type Api,
type AssistantMessage,
type AssistantMessageEventStream,
type Context,
type Model,
type SimpleStreamOptions,
calculateCost,
createAssistantMessageEventStream,
} from "@earendil-works/pi-ai";
import { mapContextToChat, type ChatHistoryItem, type ContentPart, type ToolDef } from "./context-map.js";
import { getCachedUserJwt } from "./jwt.js";
import { buildMetadata } from "./metadata.js";
import { resolveModelUid } from "./models.js";
import {
encodeFixed64Field,
encodeMessage,
encodeString,
encodeVarintField,
frameConnectStream,
iterFields,
} from "./wire.js";
const SOURCE_BY_ROLE: Record<string, number> = {
user: 1,
assistant: 2,
system: 1,
tool: 4,
};
export type CloudChatEvent =
| { kind: "text"; text: string }
| { kind: "reasoning"; text: string }
| { kind: "tool_call_start"; id: string; name: string }
| { kind: "tool_call_args"; argsDelta: string; id?: string }
| { kind: "finish"; reason: "stop" | "tool_calls" | "length" | "content_filter" }
| {
kind: "usage";
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
cachedInputTokens?: number;
cacheCreationInputTokens?: number;
};
function normalizeContent(content: string | ContentPart[]): ContentPart[] {
if (typeof content === "string") return [{ type: "text", text: content }];
return content;
}
function collapseSystemIntoUser(messages: ChatHistoryItem[]): ChatHistoryItem[] {
const out: ChatHistoryItem[] = [];
let pending: string[] = [];
const textOf = (content: string | ContentPart[]) =>
normalizeContent(content)
.filter((part) => part.type === "text" && part.text)
.map((part) => part.text as string)
.join("\n");
for (const message of messages) {
if (message.role === "system") {
const text = textOf(message.content);
if (text) pending.push(text);
continue;
}
if (message.role === "user" && pending.length > 0) {
const userParts = normalizeContent(message.content);
const userText = textOf(userParts);
const images = userParts.filter((part) => part.type === "image");
out.push({
role: "user",
content: [{ type: "text", text: `<system>\n${pending.join("\n\n")}\n</system>\n${userText}` }, ...images],
});
pending = [];
continue;
}
out.push(message);
}
if (pending.length > 0) {
out.push({ role: "user", content: `<system>\n${pending.join("\n\n")}\n</system>` });
}
return out;
}
function encodeImageData(img: { mimeType?: string; base64Data?: string }): Buffer {
return Buffer.concat([
encodeString(1, img.base64Data ?? ""),
encodeString(2, img.mimeType ?? "image/png"),
]);
}
function encodeChatToolCall(tc: { id: string; name: string; arguments: string }): Buffer {
return Buffer.concat([encodeString(1, tc.id), encodeString(2, tc.name), encodeString(3, tc.arguments)]);
}
function encodeChatMessagePrompt(
content: ContentPart[],
source: number,
opts?: { toolCallId?: string; toolCalls?: Array<{ id: string; name: string; arguments: string }> },
): Buffer {
const text = content
.filter((part) => part.type === "text")
.map((part) => part.text ?? "")
.join("\n");
const parts: Buffer[] = [
encodeVarintField(2, source),
encodeString(3, text),
encodeVarintField(4, Math.max(1, Math.floor(text.length / 4))),
encodeVarintField(5, 1),
];
if (opts?.toolCallId) parts.push(encodeString(7, opts.toolCallId));
for (const tc of opts?.toolCalls ?? []) parts.push(encodeMessage(6, encodeChatToolCall(tc)));
for (const img of content.filter((part) => part.type === "image")) {
parts.push(encodeMessage(10, encodeImageData(img)));
}
return Buffer.concat(parts);
}
function encodeCompletionConfiguration(maxOutputTokens?: number): Buffer {
return Buffer.concat([
encodeVarintField(1, 1),
encodeVarintField(2, 64_000),
encodeVarintField(3, maxOutputTokens ?? 128_000),
encodeFixed64Field(5, 0.7),
encodeFixed64Field(6, 0.95),
encodeVarintField(7, 50),
encodeFixed64Field(8, 1.0),
encodeFixed64Field(11, 1.0),
]);
}
function encodeToolDef(tool: ToolDef): Buffer {
const description = tool.description.length > 6_998 ? `${tool.description.slice(0, 6_995)}...` : tool.description;
return Buffer.concat([
encodeString(1, tool.name),
encodeString(2, description),
encodeString(3, JSON.stringify(tool.parameters ?? {})),
]);
}
function buildGetChatMessageRequest(args: {
apiKey: string;
userJwt: string;
modelUid: string;
messages: ChatHistoryItem[];
tools?: ToolDef[];
cascadeId: string;
promptId: string;
sessionId: string;
requestId: bigint;
triggerId: string;
maxOutputTokens?: number;
}): Buffer {
const metadata = buildMetadata({
apiKey: args.apiKey,
userJwt: args.userJwt,
sessionId: args.sessionId,
requestId: args.requestId,
triggerId: args.triggerId,
});
const prompts = collapseSystemIntoUser(args.messages).map((message) =>
encodeMessage(
3,
encodeChatMessagePrompt(normalizeContent(message.content), SOURCE_BY_ROLE[message.role] ?? 1, {
toolCallId: message.role === "tool" ? message.tool_call_id : undefined,
toolCalls: message.role === "assistant" ? message.tool_calls : undefined,
}),
),
);
return Buffer.concat([
encodeMessage(1, metadata),
...prompts,
encodeVarintField(7, 5),
encodeMessage(8, encodeCompletionConfiguration(args.maxOutputTokens)),
...(args.tools ?? []).map((tool) => encodeMessage(10, encodeToolDef(tool))),
encodeString(16, args.cascadeId),
encodeString(21, args.modelUid),
encodeString(22, args.promptId),
]);
}
function* decodeChatFrame(proto: Buffer): Generator<CloudChatEvent> {
for (const field of iterFields(proto)) {
if (field.num === 3 && field.wire === 2 && Buffer.isBuffer(field.value)) {
const text = field.value.toString("utf8");
if (text) yield { kind: "text", text };
} else if (field.num === 9 && field.wire === 2 && Buffer.isBuffer(field.value)) {
const text = field.value.toString("utf8");
if (text) yield { kind: "reasoning", text };
} else if (field.num === 6 && field.wire === 2 && Buffer.isBuffer(field.value)) {
let id: string | undefined;
let name: string | undefined;
let argsDelta: string | undefined;
for (const inner of iterFields(field.value)) {
if (inner.wire === 2 && Buffer.isBuffer(inner.value)) {
const text = inner.value.toString("utf8");
if (inner.num === 1) id = text;
else if (inner.num === 2) name = text;
else if (inner.num === 3) argsDelta = text;
}
}
if (id !== undefined && name !== undefined) yield { kind: "tool_call_start", id, name };
if (argsDelta !== undefined) yield { kind: "tool_call_args", argsDelta, ...(id ? { id } : {}) };
} else if (field.num === 5 && field.wire === 0) {
const value = Number(field.value);
let reason: Extract<CloudChatEvent, { kind: "finish" }>['reason'] = "stop";
if (value === 10) reason = "tool_calls";
else if (value === 11) reason = "content_filter";
else if (value === 1 || value === 3) reason = "length";
yield { kind: "finish", reason };
} else if (field.num === 28 && field.wire === 2 && Buffer.isBuffer(field.value)) {
const usage = decodeUsage(field.value);
if (usage) yield usage;
}
}
}
function decodeUsage(buf: Buffer): CloudChatEvent | null {
let promptTokens: number | undefined;
let completionTokens: number | undefined;
let cachedInputTokens: number | undefined;
let cacheCreationInputTokens: number | undefined;
for (const field of iterFields(buf)) {
if (field.num !== 2 || field.wire !== 2 || !Buffer.isBuffer(field.value)) continue;
let metric: string | undefined;
let value: number | undefined;
for (const inner of iterFields(field.value)) {
if (inner.num === 5 && inner.wire === 2 && Buffer.isBuffer(inner.value)) {
metric = inner.value.toString("utf8");
} else if (inner.num === 4 && inner.wire === 2 && Buffer.isBuffer(inner.value)) {
for (const dim of iterFields(inner.value)) {
if (dim.num === 2 && dim.wire === 5 && Buffer.isBuffer(dim.value)) {
value = dim.value.readFloatLE(0);
}
}
}
}
if (!metric || value === undefined || !Number.isFinite(value)) continue;
const n = Math.round(value);
if (metric === "input_tokens") promptTokens = n;
else if (metric === "output_tokens") completionTokens = n;
else if (metric.includes("cached") || metric.includes("cache_read")) cachedInputTokens = n;
else if (metric.includes("cache_creation")) cacheCreationInputTokens = n;
}
if (promptTokens === undefined && completionTokens === undefined) return null;
return {
kind: "usage",
promptTokens,
completionTokens,
totalTokens: (promptTokens ?? 0) + (completionTokens ?? 0),
cachedInputTokens,
cacheCreationInputTokens,
};
}
const sessionCache = new Map<string, { sessionId: string; cascadeId: string }>();
function sessionIds(apiKey: string, host: string) {
const key = `${host}\x1f${apiKey}`;
let ids = sessionCache.get(key);
if (!ids) {
ids = { sessionId: randomUUID(), cascadeId: randomUUID() };
sessionCache.set(key, ids);
}
return ids;
}
async function* streamChatEvents(args: {
apiKey: string;
host: string;
modelUid: string;
messages: ChatHistoryItem[];
tools?: ToolDef[];
maxOutputTokens?: number;
signal?: AbortSignal;
}): AsyncGenerator<CloudChatEvent> {
const host = args.host.replace(/\/$/, "");
const userJwt = await getCachedUserJwt(args.apiKey, host, args.signal);
const ids = sessionIds(args.apiKey, host);
const proto = buildGetChatMessageRequest({
apiKey: args.apiKey,
userJwt,
modelUid: args.modelUid,
messages: args.messages,
tools: args.tools,
cascadeId: ids.cascadeId,
promptId: randomUUID(),
sessionId: ids.sessionId,
requestId: BigInt(Date.now()),
triggerId: randomUUID(),
maxOutputTokens: args.maxOutputTokens,
});
const resp = await fetch(`${host}/exa.api_server_pb.ApiServerService/GetChatMessage`, {
method: "POST",
headers: {
"Content-Type": "application/connect+proto",
"Connect-Protocol-Version": "1",
"Connect-Content-Encoding": "gzip",
"Connect-Accept-Encoding": "gzip",
},
body: new Uint8Array(frameConnectStream(proto, true)),
signal: args.signal,
});
if (!resp.ok) {
throw new Error(`GetChatMessage HTTP ${resp.status}: ${(await resp.text()).slice(0, 300)}`);
}
if (!resp.body) throw new Error("GetChatMessage returned an empty body");
const reader = resp.body.getReader();
const queue: Buffer[] = [];
let queued = 0;
let sawEos = false;
let trailerError: string | null = null;
const peek = (n: number): Buffer | null => {
if (queued < n) return null;
if (queue.length === 1 && queue[0].length >= n) return queue[0].subarray(0, n);
const parts: Buffer[] = [];
let remaining = n;
for (const chunk of queue) {
if (remaining <= 0) break;
if (chunk.length <= remaining) {
parts.push(chunk);
remaining -= chunk.length;
} else {
parts.push(chunk.subarray(0, remaining));
remaining = 0;
}
}
return Buffer.concat(parts, n);
};
const drop = (n: number): void => {
queued -= n;
let remaining = n;
while (remaining > 0 && queue.length > 0) {
const head = queue[0];
if (head.length <= remaining) {
queue.shift();
remaining -= head.length;
} else {
queue[0] = head.subarray(remaining);
remaining = 0;
}
}
};
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (value) {
queue.push(Buffer.from(value));
queued += value.length;
}
while (queued >= 5) {
const header = peek(5);
if (!header) break;
const flags = header[0];
const len = header.readUInt32BE(1);
if (queued < 5 + len) break;
drop(5);
const raw = peek(len) ?? Buffer.alloc(0);
drop(len);
let payload = raw;
if (flags & 0x01) payload = zlib.gunzipSync(raw);
if (flags & 0x02) {
sawEos = true;
const text = payload.toString("utf8");
if (text.includes('"error"')) {
try {
const parsed = JSON.parse(text) as { error?: { message?: string } };
trailerError = parsed.error?.message ?? text;
} catch {
trailerError = text;
}
}
continue;
}
yield* decodeChatFrame(payload);
}
}
} finally {
try {
reader.releaseLock();
} catch {
// ignore
}
try {
void resp.body?.cancel();
} catch {
// ignore
}
}
if (trailerError) throw new Error(trailerError);
if (!sawEos) throw new Error("Devin stream ended without an EOS trailer");
}
export function streamDevin(
model: Model<Api>,
context: Context,
options?: SimpleStreamOptions,
): AssistantMessageEventStream {
const stream = createAssistantMessageEventStream();
void (async () => {
const output: AssistantMessage = {
role: "assistant",
content: [],
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
timestamp: Date.now(),
};
let textOpen = false;
let thinkingOpen = false;
let toolIndex = -1;
let partialJson = "";
let toolId = "";
let toolName = "";
const closeText = () => {
if (!textOpen) return;
const idx = output.content.length - 1;
const block = output.content[idx];
if (block.type === "text") {
stream.push({ type: "text_end", contentIndex: idx, content: block.text, partial: output });
}
textOpen = false;
};
const closeThinking = () => {
if (!thinkingOpen) return;
const idx = output.content.length - 1;
const block = output.content[idx];
if (block.type === "thinking") {
stream.push({ type: "thinking_end", contentIndex: idx, content: block.thinking, partial: output });
}
thinkingOpen = false;
};
const closeTool = () => {
if (toolIndex < 0) return;
const block = output.content[toolIndex];
if (block.type === "toolCall") {
try {
block.arguments = JSON.parse(partialJson);
} catch {
// keep last parsed object
}
stream.push({
type: "toolcall_end",
contentIndex: toolIndex,
toolCall: { type: "toolCall", id: toolId, name: toolName, arguments: block.arguments },
partial: output,
});
}
toolIndex = -1;
};
try {
const apiKey = options?.apiKey;
if (!apiKey) throw new Error("No Devin credentials. Run /login devin (uses the local Devin CLI).");
const host = (options?.env?.DEVIN_API_SERVER_URL || "https://server.codeium.com").replace(/\/$/, "");
const modelUid = resolveModelUid(model.id, model.thinkingLevelMap, options?.reasoning);
const mapped = mapContextToChat(context);
stream.push({ type: "start", partial: output });
for await (const event of streamChatEvents({
apiKey,
host,
modelUid,
messages: mapped.messages,
tools: mapped.tools.length > 0 ? mapped.tools : undefined,
maxOutputTokens: options?.maxTokens,
signal: options?.signal,
})) {
if (event.kind === "text") {
closeThinking();
if (!textOpen) {
output.content.push({ type: "text", text: "" });
stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });
textOpen = true;
}
const idx = output.content.length - 1;
const block = output.content[idx];
if (block.type === "text") {
block.text += event.text;
stream.push({ type: "text_delta", contentIndex: idx, delta: event.text, partial: output });
}
} else if (event.kind === "reasoning") {
closeText();
if (!thinkingOpen) {
output.content.push({ type: "thinking", thinking: "" });
stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
thinkingOpen = true;
}
const idx = output.content.length - 1;
const block = output.content[idx];
if (block.type === "thinking") {
block.thinking += event.text;
stream.push({ type: "thinking_delta", contentIndex: idx, delta: event.text, partial: output });
}
} else if (event.kind === "tool_call_start") {
closeText();
closeThinking();
closeTool();
toolId = event.id;
toolName = event.name;
partialJson = "";
output.content.push({ type: "toolCall", id: event.id, name: event.name, arguments: {} });
toolIndex = output.content.length - 1;
stream.push({ type: "toolcall_start", contentIndex: toolIndex, partial: output });
} else if (event.kind === "tool_call_args") {
if (toolIndex < 0) continue;
partialJson += event.argsDelta;
const block = output.content[toolIndex];
if (block.type === "toolCall") {
try {
block.arguments = JSON.parse(partialJson);
} catch {
// incomplete json
}
}
stream.push({ type: "toolcall_delta", contentIndex: toolIndex, delta: event.argsDelta, partial: output });
} else if (event.kind === "finish") {
closeText();
closeThinking();
closeTool();
output.stopReason =
event.reason === "tool_calls" ? "toolUse" : event.reason === "length" ? "length" : "stop";
} else if (event.kind === "usage") {
output.usage.input = event.promptTokens ?? 0;
output.usage.output = event.completionTokens ?? 0;
output.usage.cacheRead = event.cachedInputTokens ?? 0;
output.usage.cacheWrite = event.cacheCreationInputTokens ?? 0;
output.usage.totalTokens = event.totalTokens ?? output.usage.input + output.usage.output;
calculateCost(model, output.usage);
}
}
closeText();
closeThinking();
closeTool();
stream.push({ type: "done", reason: output.stopReason as "stop" | "length" | "toolUse", message: output });
stream.end();
} catch (error) {
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
output.errorMessage = error instanceof Error ? error.message : String(error);
stream.push({ type: "error", reason: output.stopReason as "aborted" | "error", error: output });
stream.end();
}
})();
return stream;
}
+111
View File
@@ -0,0 +1,111 @@
import * as zlib from "node:zlib";
export function encodeVarint(value: number | bigint): Buffer {
const bytes: number[] = [];
let v = BigInt(value);
if (v < 0n) throw new RangeError(`encodeVarint: negative input (${value})`);
while (v > 127n) {
bytes.push(Number(v & 0x7fn) | 0x80);
v >>= 7n;
}
bytes.push(Number(v));
return Buffer.from(bytes);
}
export function encodeTag(fieldNum: number, wire: number): Buffer {
return encodeVarint((fieldNum << 3) | wire);
}
export function encodeString(fieldNum: number, s: string): Buffer {
const buf = Buffer.from(s, "utf8");
return Buffer.concat([encodeTag(fieldNum, 2), encodeVarint(buf.length), buf]);
}
export function encodeMessage(fieldNum: number, body: Buffer): Buffer {
return Buffer.concat([encodeTag(fieldNum, 2), encodeVarint(body.length), body]);
}
export function encodeVarintField(fieldNum: number, v: number | bigint): Buffer {
return Buffer.concat([encodeTag(fieldNum, 0), encodeVarint(v)]);
}
export function encodeFixed64Field(fieldNum: number, v: number): Buffer {
const b = Buffer.alloc(8);
b.writeDoubleLE(v, 0);
return Buffer.concat([encodeTag(fieldNum, 1), b]);
}
export function encodeTimestampBody(): Buffer {
const now = Date.now();
const seconds = Math.floor(now / 1000);
const nanos = (now % 1000) * 1_000_000;
return Buffer.concat([
encodeVarintField(1, seconds),
nanos > 0 ? encodeVarintField(2, nanos) : Buffer.alloc(0),
]);
}
export function decodeVarint(buf: Buffer, offset: number): [bigint, number] {
let res = 0n;
let shift = 0n;
let i = offset;
while (i < buf.length) {
const b = buf[i++];
res |= BigInt(b & 0x7f) << shift;
if (!(b & 0x80)) return [res, i];
shift += 7n;
}
throw new Error("truncated varint");
}
export interface ProtoField {
num: number;
wire: number;
value: bigint | Buffer;
}
export function* iterFields(buf: Buffer): Generator<ProtoField> {
let i = 0;
while (i < buf.length) {
const [tagBig, next] = decodeVarint(buf, i);
i = next;
const tag = Number(tagBig);
const num = tag >> 3;
const wire = tag & 0x7;
if (wire === 0) {
const [v, after] = decodeVarint(buf, i);
i = after;
yield { num, wire, value: v };
} else if (wire === 1) {
if (i + 8 > buf.length) return;
yield { num, wire, value: buf.subarray(i, i + 8) };
i += 8;
} else if (wire === 2) {
const [n, after] = decodeVarint(buf, i);
i = after;
const len = Number(n);
if (len < 0 || i + len > buf.length) return;
yield { num, wire, value: buf.subarray(i, i + len) };
i += len;
} else if (wire === 5) {
if (i + 4 > buf.length) return;
yield { num, wire, value: buf.subarray(i, i + 4) };
i += 4;
} else {
return;
}
}
}
export function frameConnectStream(body: Buffer, compress = true): Buffer {
let payload = body;
let flags = 0;
if (compress) {
payload = zlib.gzipSync(body);
flags |= 0x01;
}
const header = Buffer.alloc(5);
header[0] = flags;
header.writeUInt32BE(payload.length, 1);
return Buffer.concat([header, payload]);
}