Files
oh-my-pi/packages/coding-agent/src/commands/complete.ts
T
can1357 1b9d9d0851 refactor(catalog)!: split model catalog from pi-ai
Move bundled models, model cache/manager, thinking metadata, effort helpers,
provider descriptors/discovery, wire constants, and model identity utilities
into the new @oh-my-pi/pi-catalog package.

Update pi-ai to keep provider runtime/auth concerns, move catalog provider
metadata into CATALOG_PROVIDERS, and migrate coding-agent, agent, stats, docs,
and tests to import catalog values from pi-catalog.

Split coding-agent model registry helpers into discovery, roles, and models
config modules while preserving registry orchestration.

BREAKING CHANGE: @oh-my-pi/pi-ai no longer exports catalog subpaths such as
/models, /model-cache, /model-manager, /model-thinking, /effort,
/provider-models*, discovery helpers, and provider wire constants; use the
matching @oh-my-pi/pi-catalog subpaths instead.
2026-06-10 04:06:57 +02:00

67 lines
2.5 KiB
TypeScript

/**
* `omp __complete <kind> [-- <prefix>]` — dynamic completion candidates.
*
* Hidden helper invoked by the generated shell completion scripts to resolve
* values that can't be baked into the script: the live model catalog and
* on-disk sessions. Output is one `value\tdescription` line per candidate
* (tab-separated); shells that show descriptions parse the tab, bash uses the
* first field. The import surface is kept deliberately narrow so a TAB press
* doesn't pay for the full agent boot.
*/
import { type GeneratedProvider, getBundledModels, getBundledProviders } from "@oh-my-pi/pi-catalog/models";
import { Command } from "@oh-my-pi/pi-utils/cli";
import { SessionManager } from "../session/session-manager";
export default class Complete extends Command {
static hidden = true;
static strict = false;
async run(): Promise<void> {
const argv = this.argv.filter(token => token !== "--");
const kind = argv[0];
const prefix = argv.length > 1 ? argv[argv.length - 1] : "";
if (kind === "models") {
completeModels(prefix);
} else if (kind === "sessions") {
await completeSessions(prefix);
}
}
}
/** Strip control chars that would corrupt the tab-separated line protocol. */
function clean(text: string): string {
return text.replace(/[\t\r\n]+/g, " ").trim();
}
function completeModels(prefix: string): void {
const needle = prefix.toLowerCase();
const seen = new Set<string>();
const lines: string[] = [];
for (const provider of getBundledProviders()) {
for (const model of getBundledModels(provider as GeneratedProvider)) {
// Offer both the fully-qualified `provider/id` and the bare `id`
// (matches the fuzzy resolution `--model` accepts).
const candidates = [`${model.provider}/${model.id}`, model.id];
for (const candidate of candidates) {
if (seen.has(candidate)) continue;
seen.add(candidate);
if (needle && !candidate.toLowerCase().includes(needle)) continue;
lines.push(`${candidate}\t${model.provider}`);
}
}
}
lines.sort();
if (lines.length > 0) process.stdout.write(`${lines.join("\n")}\n`);
}
async function completeSessions(prefix: string): Promise<void> {
const sessions = await SessionManager.list(process.cwd());
const lines: string[] = [];
for (const session of sessions) {
if (prefix && !session.id.startsWith(prefix)) continue;
const label = clean(session.title ?? session.firstMessage ?? "").slice(0, 72);
lines.push(`${session.id}\t${label}`);
}
if (lines.length > 0) process.stdout.write(`${lines.join("\n")}\n`);
}