Files
pi-devin/src/jwt.ts
T
Kashyab Ambarani d36c0b3705 Initial pi-devin package: Devin Local models inside Pi.
Auth and catalog come from the local Devin CLI. Pi stays the harness.
2026-08-16 20:29:56 -07:00

104 lines
3.3 KiB
TypeScript

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();
}