@zosmaai/pi-llm-wiki
Version:
Self-maintaining LLM Wiki for Pi — Karpathy-pattern knowledge base with immutable source capture, automated ingestion, search, linting, and Obsidian-compatible vault. auto-updating personal & company wiki.
129 lines (113 loc) • 5.11 kB
text/typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import type { Runtime } from "./runtime.js";
import { type TaskConfig, parseModelRef, persistTaskModel } from "./task-config.js";
/**
* Model selection surface for the wiki background lane (issue #69, epic #63).
*
* The `taskModel` config field (read by `Runtime.resolveModel`) already exists;
* this module adds the user-facing *surface* to view and set it:
* - the `/wiki-model` slash command (interactive picker + scriptable arg),
* - a status-bar label of the active task model,
* - the per-call override is wired on heavy tools (e.g. `wiki_ingest`).
*
* The default is always the session model: when no `taskModel` is configured
* and no override is passed, background work runs on the current session model.
*/
/** A minimal view of a registry model (provider + id, optional display name). */
interface ModelLike {
provider: string;
id: string;
name?: string;
}
/** Words that clear the override and revert to the session model. */
const CLEAR_WORDS = new Set(["session", "default", "reset", "clear", "none", "unset"]);
/** The status-bar key for the active-model label (so we can update it in place). */
export const MODEL_STATUS_KEY = "llm-wiki-model";
/**
* Human-readable label for the active background task model. Shows the
* configured `provider/id` when set, otherwise the session model (with its id
* when known). Pure — safe to unit test and reuse for the status line.
*/
export function formatActiveModelLabel(config: TaskConfig, sessionModelId?: string): string {
if (config.taskModel) return `${config.taskModel.provider}/${config.taskModel.id}`;
return sessionModelId ? `session model (${sessionModelId})` : "session model";
}
/** "provider/id" ref for a model. */
function modelRef(m: ModelLike): string {
return `${m.provider}/${m.id}`;
}
/**
* Register the `/wiki-model` slash command. Lets the user view the active
* background task model and choose another (or revert to the session model).
* The choice is persisted to project settings and applied immediately.
*
* /wiki-model → interactive picker (lists available models)
* /wiki-model provider/id → set directly (scriptable / no UI needed)
* /wiki-model session|clear → clear the override, use the session model
*/
export function registerWikiModelCommand(pi: ExtensionAPI, runtime: Runtime): void {
pi.registerCommand("wiki-model", {
description:
"View or set the model used for LLM Wiki background tasks (default: session model)",
handler: async (args, ctx) => {
runtime.ensureConfig(ctx.cwd);
const sessionId = (ctx.model as ModelLike | undefined)?.id;
const apply = (model: { provider: string; id: string } | undefined): void => {
persistTaskModel(ctx.cwd, model);
runtime.config = { ...runtime.config, taskModel: model };
runtime.configLoaded = true;
const label = formatActiveModelLabel(runtime.config, sessionId);
ctx.ui.setStatus(MODEL_STATUS_KEY, `🧠 wiki model: ${label}`);
ctx.ui.notify(`LLM Wiki: background tasks now use ${label}`, "info");
};
const trimmed = args.trim();
// Explicit clear → session model.
if (trimmed && CLEAR_WORDS.has(trimmed.toLowerCase())) {
apply(undefined);
return;
}
// Direct "provider/id" set (works without UI).
if (trimmed) {
const ref = parseModelRef(trimmed);
if (!ref) {
ctx.ui.notify(
`LLM Wiki: could not parse "${trimmed}". Use provider/id (e.g. anthropic/claude-haiku) or "session".`,
"error",
);
return;
}
const found = ctx.modelRegistry.find(ref.provider, ref.id) as ModelLike | undefined;
if (!found) {
ctx.ui.notify(
`LLM Wiki: model ${ref.provider}/${ref.id} is not in the registry (run /wiki-model with no argument to pick from available models).`,
"error",
);
return;
}
apply({ provider: found.provider, id: found.id });
return;
}
// No argument: interactive picker.
const current = formatActiveModelLabel(runtime.config, sessionId);
if (!ctx.hasUI) {
ctx.ui.notify(
`LLM Wiki: active background model is ${current}. Pass provider/id to change it (no interactive UI here).`,
"info",
);
return;
}
const available = (ctx.modelRegistry.getAvailable() as ModelLike[]) ?? [];
const pool = available.length > 0 ? available : (ctx.modelRegistry.getAll() as ModelLike[]);
const sessionOption = "↩ Use session model (clear override)";
const options = [sessionOption, ...pool.map(modelRef)];
const picked = await ctx.ui.select(`Wiki background model (current: ${current})`, options);
if (picked === undefined) return; // cancelled
if (picked === sessionOption) {
apply(undefined);
return;
}
const ref = parseModelRef(picked);
if (ref) apply(ref);
},
});
}