Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/model-info/index.ts

Raw
/**
 * Model route footer segments.
 *
 * Owns executor model, fast transport, and Angel advisor presentation.
 */

import type {
	ExtensionAPI,
	ExtensionContext,
} from "@earendil-works/pi-coding-agent";
import { shortModelLabel } from "./model-label.ts";
import { closeDebug, dbg } from "./src/debug.ts";
import {
	type FooterSegment,
	offerFooterSegment,
	removeFooterSegment,
} from "./src/pi-ext-footer-segment.ts";

const STATUS_ID = "model-info";
const GROUP_ID = "model-route";
const PROVIDER_SEGMENT_ID = "provider";
const SUBSCRIPTION_SEGMENT_ID = "subscription";
const MODEL_SEGMENT_ID = "model";
const FAST_SEGMENT_ID = "fast";
const ANGEL_SEGMENT_ID = "angel";
const SERVED_SEGMENT_ID = "served";
const FAST_ICON = "󱐋";
const ANGEL_ICON = "󰧑";
const SUBSCRIPTION_ICON = "󱏐";
const PROVIDER_ICONS: Readonly<Record<string, string>> = {
	anthropic: "",
	klaus: "",
	openai: "",
	"openai-codex": "",
	"github-copilot": "",
	google: "",
	"google-gemini-cli": "",
	zai: "󰬇",
	hetzner: "󰰀",
	grts: "󰙺",
};

type AngelState = { modelId: string; thinkingLevel: string };

type ReplyMessage = {
	role?: unknown;
	provider?: unknown;
	model?: unknown;
	responseModel?: unknown;
};

/**
 * The model that actually answered, when a router model (grts/auto,
 * OpenRouter auto, kilo-auto/free) resolved to a different one.
 */
function servedModel(
	message: ReplyMessage | undefined,
	ctx: Pick<ExtensionContext, "model">,
): string | undefined {
	if (
		message?.role !== "assistant" ||
		message.provider !== ctx.model?.provider ||
		message.model !== ctx.model?.id ||
		typeof message.responseModel !== "string" ||
		message.responseModel === message.model
	)
		return undefined;
	return message.responseModel;
}

function servedFromBranch(
	ctx: Pick<ExtensionContext, "model" | "sessionManager">,
): string | undefined {
	const branch = ctx.sessionManager.getBranch();
	for (let i = branch.length - 1; i >= 0; i--) {
		const entry = branch[i] as
			| { type?: unknown; message?: ReplyMessage }
			| undefined;
		if (entry?.type === "message" && entry.message?.role === "assistant")
			return servedModel(entry.message, ctx);
	}
	return undefined;
}

function modelLabel(ctx: Pick<ExtensionContext, "model">): string {
	return shortModelLabel(ctx.model?.id ?? "no-model");
}

function providerLabel(ctx: Pick<ExtensionContext, "model">): string {
	return ctx.model?.provider ?? "no-provider";
}

function providerIcon(
	ctx: Pick<ExtensionContext, "model">,
): string | undefined {
	return PROVIDER_ICONS[providerLabel(ctx)];
}

function usesSubscription(
	ctx: Pick<ExtensionContext, "model" | "modelRegistry">,
): boolean {
	const model = ctx.model;
	if (!model) return false;
	if (model.provider === "kimi-coding") return true;
	return (
		ctx.modelRegistry.isUsingOAuth(model) &&
		ctx.modelRegistry.getProvider(model.provider)?.auth.oauth
			?.isSubscription === true
	);
}

function thinkingLevelFromBranch(
	ctx: Pick<ExtensionContext, "sessionManager">,
): string {
	const branch = ctx.sessionManager.getBranch();
	for (let i = branch.length - 1; i >= 0; i--) {
		const entry = branch[i] as
			| { type?: unknown; thinkingLevel?: unknown }
			| undefined;
		if (
			entry?.type === "thinking_level_change" &&
			typeof entry.thinkingLevel === "string"
		)
			return entry.thinkingLevel;
	}
	return "off";
}

function modelInfoLabel(
	ctx: Pick<ExtensionContext, "model" | "sessionManager">,
	thinking?: string,
): string {
	return `${modelLabel(ctx)}:${thinking ?? thinkingLevelFromBranch(ctx)}`;
}

function clearSegments(pi: ExtensionAPI): void {
	for (const id of [
		PROVIDER_SEGMENT_ID,
		SUBSCRIPTION_SEGMENT_ID,
		MODEL_SEGMENT_ID,
		SERVED_SEGMENT_ID,
		FAST_SEGMENT_ID,
		ANGEL_SEGMENT_ID,
	])
		removeFooterSegment(pi, id);
}

function statusLabel(
	model: string,
	fast: boolean,
	angel: AngelState | undefined,
	served?: string,
): string {
	const parts = [model];
	if (served) parts.push(`→ ${shortModelLabel(served)}`);
	if (fast) parts.push(FAST_ICON);
	if (angel)
		parts.push(
			`→ ${ANGEL_ICON} ${shortModelLabel(angel.modelId)}:${angel.thinkingLevel}`,
		);
	return parts.join(" ");
}

export default function modelInfoExtension(pi: ExtensionAPI): void {
	let currentCtx: ExtensionContext | undefined;
	let fast = false;
	let angel: AngelState | undefined;
	let served: string | undefined;

	function publish(ctx: ExtensionContext, thinking?: string): void {
		const model = modelInfoLabel(ctx, thinking);
		ctx.ui.setStatus(FAST_SEGMENT_ID, undefined);
		ctx.ui.setStatus(ANGEL_SEGMENT_ID, undefined);
		if (ctx.mode !== "tui") {
			clearSegments(pi);
			ctx.ui.setStatus(STATUS_ID, statusLabel(model, fast, angel, served));
			dbg?.("route.refresh", { mode: ctx.mode, outcome: "status" });
			return;
		}

		let accepted = true;
		const offer = (segment: FooterSegment) => {
			accepted = offerFooterSegment(pi, segment) && accepted;
		};
		const remove = (id: string) => {
			accepted = removeFooterSegment(pi, id) && accepted;
		};
		const provider = providerIcon(ctx);
		offer({
			id: PROVIDER_SEGMENT_ID,
			text: provider ? "" : providerLabel(ctx),
			icon: provider,
			color: "dim",
			zone: "llm",
			group: GROUP_ID,
			order: -0.5,
		});
		if (usesSubscription(ctx))
			offer({
				id: SUBSCRIPTION_SEGMENT_ID,
				text: "",
				icon: SUBSCRIPTION_ICON,
				color: "success",
				zone: "llm",
				group: GROUP_ID,
				gap: 2,
				order: -0.25,
			});
		else remove(SUBSCRIPTION_SEGMENT_ID);
		offer({
			id: MODEL_SEGMENT_ID,
			text: model,
			color: "syntaxKeyword",
			zone: "llm",
			group: GROUP_ID,
			gap: 2,
			order: 0,
		});
		if (served)
			offer({
				id: SERVED_SEGMENT_ID,
				text: shortModelLabel(served),
				color: "dim",
				zone: "llm",
				group: GROUP_ID,
				separator: "→",
				order: 0.25,
			});
		else remove(SERVED_SEGMENT_ID);
		if (fast)
			offer({
				id: FAST_SEGMENT_ID,
				text: "",
				icon: FAST_ICON,
				color: "warning",
				zone: "llm",
				group: GROUP_ID,
				order: 0.5,
			});
		else remove(FAST_SEGMENT_ID);
		if (angel)
			offer({
				id: ANGEL_SEGMENT_ID,
				text: `${shortModelLabel(angel.modelId)}:${angel.thinkingLevel}`,
				icon: ANGEL_ICON,
				color: "accent",
				zone: "llm",
				group: GROUP_ID,
				separator: "→",
				order: 0.75,
			});
		else remove(ANGEL_SEGMENT_ID);

		if (!accepted) clearSegments(pi);
		ctx.ui.setStatus(
			STATUS_ID,
			accepted ? undefined : statusLabel(model, fast, angel, served),
		);
		dbg?.("route.refresh", {
			mode: ctx.mode,
			outcome: accepted ? "footer" : "fallback",
		});
	}

	function refresh(ctx: ExtensionContext, thinking?: string): void {
		currentCtx = ctx;
		if (!ctx.hasUI) {
			dbg?.("route.refresh", { mode: ctx.mode, outcome: "no_ui" });
			return;
		}
		publish(ctx, thinking ?? ctx.thinkingLevel);
	}

	pi.events.on("model-route:fast", (data: unknown) => {
		if (typeof (data as { active?: unknown })?.active !== "boolean") return;
		fast = (data as { active: boolean }).active;
		if (currentCtx) refresh(currentCtx);
	});
	pi.events.on("model-route:angel", (data: unknown) => {
		const candidate = (data as { advisor?: unknown })?.advisor;
		if (candidate === undefined) angel = undefined;
		else if (
			typeof (candidate as AngelState)?.modelId === "string" &&
			typeof (candidate as AngelState)?.thinkingLevel === "string"
		)
			angel = candidate as AngelState;
		else return;
		if (currentCtx) refresh(currentCtx);
	});

	pi.on("session_start", (_event, ctx) => {
		dbg?.("session.start", { mode: ctx.mode });
		served = servedFromBranch(ctx);
		refresh(ctx);
	});
	pi.on("model_select", (_event, ctx) => {
		served = undefined;
		refresh(ctx);
	});
	pi.on("message_end", (event, ctx) => {
		if ((event.message as ReplyMessage).role !== "assistant") return;
		const next = servedModel(event.message as ReplyMessage, ctx);
		if (next === served) return;
		served = next;
		refresh(ctx);
	});
	pi.on("thinking_level_select", (event, ctx) => refresh(ctx, event.level));
	pi.on("session_compact", (_event, ctx) => refresh(ctx));
	pi.on("session_shutdown", (_event, ctx) => {
		dbg?.("session.shutdown");
		clearSegments(pi);
		if (ctx.hasUI) ctx.ui.setStatus(STATUS_ID, undefined);
		currentCtx = undefined;
		fast = false;
		angel = undefined;
		served = undefined;
		closeDebug();
	});
}

export const __test = {
	modelInfoLabel,
	modelLabel,
	providerIcon,
	providerLabel,
	servedFromBranch,
	servedModel,
	statusLabel,
	usesSubscription,
	thinkingLevelFromBranch,
};