Files
oh-my-pi/packages/coding-agent/src/slash-commands/acp-builtins.ts
T
can1357 e9888367d1 refactor: migrated packages to internal utility modules and removed external dependencies
- Implemented in-house, zero-dependency utility modules in `pi-utils` covering DOM manipulation, markdown parsing, templating, browser automation helpers, and terminal buffers.
- Migrated packages across the repository to consume the new internal utilities and `omptype` schema validators instead of external dependencies.
- Removed multiple external runtime and development dependencies including Zod, Marked, LRU cache, Turndown, and Puppeteer browser packages.
2026-08-05 13:39:09 +02:00

71 lines
3.0 KiB
TypeScript

import type { AvailableCommand } from "@oh-my-pi/pi-utils/acp";
import { BUILTIN_SLASH_COMMANDS_INTERNAL, lookupBuiltinSlashCommand } from "./builtin-registry";
import { parseSlashCommand } from "./helpers/parse";
import type { AcpBuiltinSlashCommandResult, SlashCommandRuntime } from "./types";
export type { AcpBuiltinSlashCommandResult } from "./types";
/**
* All names (primary + aliases) that are reserved by ACP builtins. Used to
* filter out extension commands that would shadow a builtin or its alias at
* dispatch time (e.g. `models` is an alias for `/model`, so an extension
* registering `models` would appear in the palette but execute the builtin).
*/
export const ACP_BUILTIN_RESERVED_NAMES: ReadonlySet<string> = new Set(
BUILTIN_SLASH_COMMANDS_INTERNAL.filter(c => c.handle !== undefined).flatMap(c => [c.name, ...(c.aliases ?? [])]),
);
/**
* Whether an extension command named `name` would be captured by ACP builtin
* dispatch before reaching the extension handler. Beyond exact name/alias
* collisions, `parseSlashCommand` treats `:` as a name/args separator, so a
* colon-namespaced name whose prefix is a handled builtin (e.g. `model:foo`)
* executes the `/model` builtin with `foo` as args. Such names must not be
* advertised to ACP clients.
*/
export function isAcpBuiltinShadowedName(name: string): boolean {
if (ACP_BUILTIN_RESERVED_NAMES.has(name)) return true;
const colon = name.indexOf(":");
return colon !== -1 && ACP_BUILTIN_RESERVED_NAMES.has(name.slice(0, colon));
}
/**
* Commands advertised to ACP clients. Entries without a text-mode `handle`
* (e.g. `/quit`, `/login`, dashboards) are filtered out so the client doesn't
* see commands it cannot drive.
*/
export const ACP_BUILTIN_SLASH_COMMANDS: AvailableCommand[] = BUILTIN_SLASH_COMMANDS_INTERNAL.filter(
command => command.handle !== undefined,
).map(command => {
// Honor mode-specific copy: ACP clients receive concise text-mode
// descriptions/hints when the spec sets `acpDescription` / `acpInputHint`,
// otherwise fall back to the unified `description` / `inlineHint`.
const hint = command.acpInputHint ?? command.inlineHint;
return {
name: command.name,
description: command.acpDescription ?? command.description,
input: hint ? { hint } : undefined,
};
});
/**
* Dispatch a slash command in ACP/text mode. Returns:
* - `false` when no builtin matched (or matched a TUI-only entry); the caller
* should forward the input as a prompt.
* - `{ consumed: true }` when the command handled the input entirely.
* - `{ prompt }` when the command was handled but a residual prompt should be
* sent to the model.
*/
export async function executeAcpBuiltinSlashCommand(
text: string,
runtime: SlashCommandRuntime,
): Promise<AcpBuiltinSlashCommandResult> {
const parsed = parseSlashCommand(text);
if (!parsed) return false;
const command = lookupBuiltinSlashCommand(parsed.name);
if (!command?.handle) return false;
const result = await command.handle(parsed, runtime);
if (result === undefined) return { consumed: true };
return result;
}