/** * @fileoverview Command definition and registry types * * Commands are the core abstraction in AFD. Every application action * is defined as a command with a clear schema. */ import type { BatchRequest, BatchResult } from './batch.js'; import { type CommandToolMeta } from './command-metadata.js'; /** * Validate that a command name follows the `domain-action` kebab-case convention. * * @param name - The command name to validate * @returns An object with `valid` and optional `reason` if invalid */ export declare function validateCommandName(name: string): { valid: boolean; reason?: string; }; import type { CommandResult } from './result.js'; import type { StreamChunk, StreamOptions } from './streaming.js'; /** * Controls which interfaces a command is exposed to. */ export interface ExposeOptions { /** Command palette (default: true) */ palette?: boolean; /** External MCP agents (default: false — opt-in for security) */ mcp?: boolean; /** In-app AI assistant (default: true) */ agent?: boolean; /** Terminal/CLI (default: false) */ cli?: boolean; } /** Frozen to prevent accidental mutation */ export declare const defaultExpose: Readonly; /** * Whether a command is exposed to an interface. * * Each flag a command's `expose` leaves out falls back to {@link defaultExpose}, so * `expose: { mcp: true }` keeps the default agent and palette exposure. */ export declare function isExposedTo(command: { expose?: ExposeOptions; }, interfaceType: keyof ExposeOptions): boolean; /** * JSON Schema 7 subset for command parameter validation. * * Includes composition keywords (`oneOf`, `anyOf`, `allOf`) needed for * discriminated unions, non-discriminated unions, and intersections. * These are produced by Zod's `z.toJSONSchema()` with `target: 'draft-7'`, * which emits `'integer'` for `z.number().int()`. */ export interface JsonSchema { type: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null'; description?: string; /** For object schemas, array of required property names */ required?: string[] | boolean; default?: unknown; enum?: unknown[]; items?: JsonSchema; properties?: Record; additionalProperties?: boolean | JsonSchema; minimum?: number; maximum?: number; exclusiveMinimum?: number; exclusiveMaximum?: number; minLength?: number; maxLength?: number; minItems?: number; maxItems?: number; pattern?: string; format?: string; /** Exactly one schema must match — used for discriminated unions (`z.discriminatedUnion()`) */ oneOf?: JsonSchema[]; /** At least one schema must match — used for unions (`z.union()`) and nullable types */ anyOf?: JsonSchema[]; /** All schemas must match — used for intersections (`z.intersection()`) */ allOf?: JsonSchema[]; /** Schema must not match */ not?: JsonSchema; /** Exact value match — used for discriminator literals (`z.literal()`) */ const?: unknown; } /** * Definition for a single command parameter. */ export interface CommandParameter { /** Parameter name */ name: string; /** JSON Schema type */ type: JsonSchema['type']; /** Human-readable description */ description: string; /** Whether this parameter is required */ required?: boolean; /** Default value if not provided */ default?: unknown; /** For enum types, the allowed values */ enum?: unknown[]; /** Full JSON Schema for complex validation */ schema?: JsonSchema; } /** * A concrete input example for a command. * Used to help agents understand valid input shapes without reverse-engineering JSON Schema. */ export interface CommandExample { /** Short description of what this example demonstrates */ title: string; /** A valid input payload */ input: TInput; } /** * Full command definition with schema, handler, and metadata. * * @template TInput - Type of the input parameters * @template TOutput - Type of the result data * * @example * ```typescript * const createDocument: CommandDefinition = { * name: 'document-create', * description: 'Creates a new document', * category: 'documents', * parameters: [ * { name: 'title', type: 'string', description: 'Document title', required: true }, * { name: 'content', type: 'string', description: 'Document content' } * ], * returns: { * type: 'object', * description: 'The created document' * }, * handler: async (input) => { * // Implementation * return success({ id: '123', title: input.title }); * } * }; * ``` */ export interface CommandDefinition { /** * Unique command name using kebab-case. * * Convention: `domain-action` (e.g., 'todo-create', 'user-get', 'todo-create-batch') */ name: string; /** * Human-readable description of what the command does. * This is shown in tool listings and documentation. */ description: string; /** * Category for grouping related commands. * Used for filtering in `afd tools --category ` */ category?: string; /** * Command parameters with types and descriptions. */ parameters: CommandParameter[]; /** * Schema describing the return type. */ returns?: JsonSchema; /** * Error codes this command may return. */ errors?: string[]; /** * The command implementation. * Returns a CommandResult with data or error. */ handler: CommandHandler; /** * Command version for tracking changes. */ version?: string; /** * Tags for additional categorization. */ tags?: string[]; /** Commands that should be called before this one. Metadata only — not enforced at runtime. */ requires?: string[]; /** * Whether this command performs side effects. */ mutation?: boolean; /** * Estimated execution time category. */ executionTime?: 'instant' | 'fast' | 'slow' | 'long-running'; /** * Whether this command returns a HandoffResult for protocol handoffs. * When true, the command's data payload will be a HandoffResult * that the client should use to connect to the specified protocol. */ handoff?: boolean; /** * The specific handoff protocol this command uses (if handoff is true). * Used for agent filtering (e.g., find all WebSocket commands). */ handoffProtocol?: 'websocket' | 'webrtc' | 'sse' | 'http-stream' | string; /** * Whether this command performs destructive or irreversible actions. * When true, frontends SHOULD prompt for user confirmation before * applying the result locally. */ destructive?: boolean; /** * Custom confirmation prompt message for destructive commands. * If not provided, frontends MAY use a generic confirmation message. * @example "Delete 'Buy groceries' permanently?" */ confirmPrompt?: string; /** * Whether this command can be undone. * Implementation is consumer-specific (e.g., FAST-AF uses `${methodName}Undo()` convention). */ undoable?: boolean; /** * Which interfaces this command is exposed to. * Defaults to `defaultExpose` if not specified. */ expose?: ExposeOptions; /** * Concrete input examples to help agents construct valid payloads. * Each example is validated against the input schema at define-time. */ examples?: CommandExample[]; /** * Contexts this command belongs to. When context-based tool scoping is enabled, * only commands matching the active context (or commands without contexts) are visible. */ contexts?: string[]; } /** * Command handler function type. */ export type CommandHandler = (input: TInput, context?: CommandContext) => Promise>; /** * Context provided to command handlers. */ export interface CommandContext { /** Unique ID for this command invocation */ traceId?: string; /** Timeout in milliseconds */ timeout?: number; /** Signal for cancellation */ signal?: AbortSignal; /** The interface invoking this command (for exposure checks) */ interface?: keyof ExposeOptions; /** Custom context values */ [key: string]: unknown; } /** * Middleware function type for intercepting command execution. * * Middleware wraps command execution in an onion pattern — each middleware * calls `next()` to invoke the next layer, and can inspect/modify the * result on the way back out. */ export type CommandMiddleware = (commandName: string, input: unknown, context: CommandContext, next: () => Promise) => Promise; /** * Registry for managing command definitions. */ export interface CommandRegistry { /** * Register a command. * @throws If a command with the same name already exists */ register(command: CommandDefinition): void; /** * Get a command by name. * @returns The command definition or undefined if not found */ get(name: string): CommandDefinition | undefined; /** * Check if a command exists. */ has(name: string): boolean; /** * Get all registered commands. */ list(): CommandDefinition[]; /** * Get commands by category. */ listByCategory(category: string): CommandDefinition[]; /** * Get commands by tags. * @param tags - Array of tags to filter by * @param mode - 'all' requires commands to have ALL tags, 'any' requires at least one tag * @returns Commands matching the tag filter */ listByTags(tags: string[], mode: 'all' | 'any'): CommandDefinition[]; /** * Get commands exposed to a specific interface. * @param interfaceType - The interface to filter by (mcp, cli, palette, agent) * @returns Commands exposed to the specified interface */ listByExposure(interfaceType: keyof ExposeOptions): CommandDefinition[]; /** * Execute a command by name. */ execute(name: string, input: unknown, context?: CommandContext): Promise>; /** * Execute multiple commands in a single batch. * * Uses partial success semantics - returns results for all commands * even if some fail. Confidence is aggregated from success ratio * and individual command confidence scores. * * The envelope is validated before any command runs (a malformed or empty * batch returns `INVALID_BATCH_REQUEST`), `options.timeout` is enforced as a * deadline at any `parallelism`, and every entry runs through `execute()` * with `context`, so exposure checks apply to each entry. * * @param request - Batch request containing commands and options * @param context - Context passed to every command (e.g. `interface`, `signal`, `traceId`) * @returns BatchResult with all command results and aggregated metrics */ executeBatch(request: BatchRequest, context?: CommandContext): Promise>; /** * Execute a command and return its result as stream chunks. * * Returns an AsyncGenerator of StreamChunks. The command runs through * `execute()` with `context` (so exposure checks apply) and completes before * any chunk is produced: an array result becomes one data chunk per item, * anything else one data chunk, followed by a complete chunk (or an error * chunk). It does not stream incrementally or emit progress chunks. * * @param name - Command name * @param input - Command input * @param options - Stream options: `signal` cancels the stream, and `timeout` is a deadline for the whole stream (it ends with a `STREAM_TIMEOUT` error chunk) * @param context - Context passed to the command (e.g. `interface`); `options.signal` takes precedence over `context.signal` * @returns AsyncGenerator yielding StreamChunks */ executeStream(name: string, input: unknown, options?: StreamOptions, context?: CommandContext): AsyncGenerator, void, unknown>; } /** * Options for {@link createCommandRegistry}. */ export interface CommandRegistryOptions { /** * Include raw exception messages and stack traces in `COMMAND_EXECUTION_ERROR` * results. Default `false`, matching `devMode` in `createMcpServer()`. */ devMode?: boolean; } /** * Create a new command registry. * * `execute`, `executeBatch` and `executeStream` share one execution path: * batch and stream delegate to the core `executeBatch()` / `executeStream()` * helpers with `execute()` as the callback, so exposure checks, the batch * deadline and error redaction behave the same on every entry point. */ export declare function createCommandRegistry(options?: CommandRegistryOptions): CommandRegistry; /** * Convert a CommandDefinition to MCP tool format. */ export declare function commandToMcpTool(command: CommandDefinition): { _meta?: CommandToolMeta; name: string; description: string; inputSchema: { type: 'object'; properties: Record; required: string[]; }; }; /** * Check if a command is exposed to MCP. */ export declare function isMcpExposed(command: Pick): boolean; /** * Convert commands to MCP tools, filtering by MCP exposure. * Only commands with `expose.mcp === true` will be included. */ export declare function commandsToMcpTools(commands: CommandDefinition[]): ReturnType[]; //# sourceMappingURL=commands.d.ts.map