- 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.
71 lines
3.0 KiB
TypeScript
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;
|
|
}
|