/** * FluentToolBuilder — Type-Chaining Builder for Semantic Verb Tools * * The core builder behind `f.query()`, `f.mutation()`, and `f.action()`. * Uses TypeScript generic accumulation so that each fluent step narrows * the types — the IDE "magically" knows the exact shape of `input` and * `ctx` inside `.handle()` without any manual Interface declaration. * * @example * ```typescript * const f = initFusion(); * * const listUsers = f.query('users.list') * .describe('List users from the database') * .withNumber('limit', 'Max results to return') * .withOptionalEnum('status', ['active', 'inactive'], 'Filter by status') * .returns(UserPresenter) * .handle(async (input, ctx) => { * return ctx.db.user.findMany({ take: input.limit }); * }); * ``` * * @see {@link FluentRouter} for prefix grouping * @see {@link initFusion} for the factory that creates these builders * * @module */ import { type ZodType, type ZodObject, type ZodRawShape } from 'zod'; import { GroupedToolBuilder } from './GroupedToolBuilder.js'; import { type ToolResponse, type MiddlewareFn } from '../types.js'; import { type Presenter } from '../../presenter/Presenter.js'; import { type ConcurrencyConfig } from '../execution/ConcurrencyGuard.js'; import { type SandboxConfig } from '../../sandbox/SandboxEngine.js'; import { type MiddlewareDefinition } from '../middleware/ContextDerivation.js'; /** * Semantic defaults applied by each verb. * @internal */ export interface SemanticDefaults { readonly readOnly?: boolean; readonly destructive?: boolean; readonly idempotent?: boolean; } /** Defaults for `f.query()` — read-only, no side effects */ export declare const QUERY_DEFAULTS: SemanticDefaults; /** Defaults for `f.mutation()` — destructive, irreversible */ export declare const MUTATION_DEFAULTS: SemanticDefaults; /** Defaults for `f.action()` — neutral, no assumptions */ export declare const ACTION_DEFAULTS: SemanticDefaults; /** * Fluent builder that accumulates types at each step. * * @typeParam TContext - Base application context (from `initFusion()`) * @typeParam TInput - Accumulated input type (built by `with*()` methods) * @typeParam TCtx - Accumulated context type (enriched by `.use()`) */ export declare class FluentToolBuilder { /** @internal */ readonly _name: string; /** @internal */ _description?: string; /** @internal */ _instructions?: string; /** @internal */ _inputSchema?: ZodObject; /** @internal */ _withParams: Record; /** @internal */ _tags: string[]; /** * @internal Bug #118 fix: reject duplicate parameter names. * All `withXxx()` methods delegate to this instead of assigning directly. */ private _addParam; /** @internal */ _middlewares: MiddlewareFn[]; /** @internal */ _returns?: Presenter; /** @internal */ _semanticDefaults: SemanticDefaults; /** @internal */ _readOnly?: boolean; /** @internal */ _destructive?: boolean; /** @internal */ _idempotent?: boolean; /** @internal */ _toonMode: boolean; /** @internal */ _annotations?: Record; /** @internal */ _invalidatesPatterns: string[]; /** @internal */ _cacheControl?: 'no-store' | 'immutable'; /** @internal */ _concurrency?: ConcurrencyConfig; /** @internal */ _egressMaxBytes?: number; /** @internal */ _sandboxConfig?: SandboxConfig; /** @internal */ _handlerSet: boolean; /** @internal */ _fsmStates?: string[]; /** @internal */ _fsmTransition?: string; /** * @param name - Tool name in `domain.action` format (e.g. `'users.list'`) * @param defaults - Semantic defaults from the verb (`query`, `mutation`, `action`) */ constructor(name: string, defaults?: SemanticDefaults); /** * Set the tool description shown to the LLM. * * @param text - Human-readable description * @returns `this` for chaining */ describe(text: string): FluentToolBuilder; /** * Set AI-First instructions — injected as system-level guidance in the tool description. * * This is **Prompt Engineering embedded in the framework**. The instructions * tell the LLM WHEN and HOW to use this tool, reducing hallucination. * * @param text - System prompt for the tool * @returns `this` for chaining * * @example * ```typescript * f.query('docs.search') * .describe('Search internal documentation') * .instructions('Use ONLY when the user asks about internal policies.') * .withString('query', 'Search term') * .handle(async (input) => { ... }); * ``` */ instructions(text: string): FluentToolBuilder; /** * Add a required string parameter. * * @param name - Parameter name * @param description - Human-readable description for the LLM * @returns Builder with narrowed `TInput` type * * @example * ```typescript * f.query('projects.get') * .withString('project_id', 'The project ID to retrieve') * .handle(async (input) => { ... }); * // input.project_id: string ✅ * ``` */ withString(name: K, description?: string): FluentToolBuilder, TCtx>; /** * Add an optional string parameter. * * @param name - Parameter name * @param description - Human-readable description for the LLM * @returns Builder with narrowed `TInput` type */ withOptionalString(name: K, description?: string): FluentToolBuilder>, TCtx>; /** * Add a required number parameter. * * @param name - Parameter name * @param description - Human-readable description for the LLM * @returns Builder with narrowed `TInput` type */ withNumber(name: K, description?: string): FluentToolBuilder, TCtx>; /** * Add an optional number parameter. * * @param name - Parameter name * @param description - Human-readable description for the LLM * @returns Builder with narrowed `TInput` type */ withOptionalNumber(name: K, description?: string): FluentToolBuilder>, TCtx>; /** * Add a required boolean parameter. * * @param name - Parameter name * @param description - Human-readable description for the LLM * @returns Builder with narrowed `TInput` type */ withBoolean(name: K, description?: string): FluentToolBuilder, TCtx>; /** * Add an optional boolean parameter. * * @param name - Parameter name * @param description - Human-readable description for the LLM * @returns Builder with narrowed `TInput` type */ withOptionalBoolean(name: K, description?: string): FluentToolBuilder>, TCtx>; /** * Add a required enum parameter. * * @param name - Parameter name * @param values - Allowed enum values * @param description - Human-readable description for the LLM * @returns Builder with narrowed `TInput` type * * @example * ```typescript * f.query('invoices.list') * .withEnum('status', ['draft', 'sent', 'paid'], 'Filter by status') * .handle(async (input) => { ... }); * // input.status: 'draft' | 'sent' | 'paid' ✅ * ``` */ withEnum(name: K, values: readonly [V, ...V[]], description?: string): FluentToolBuilder, TCtx>; /** * Add an optional enum parameter. * * @param name - Parameter name * @param values - Allowed enum values * @param description - Human-readable description for the LLM * @returns Builder with narrowed `TInput` type */ withOptionalEnum(name: K, values: readonly [V, ...V[]], description?: string): FluentToolBuilder>, TCtx>; /** * Add a required array parameter. * * @param name - Parameter name * @param itemType - Type of array items (`'string'`, `'number'`, `'boolean'`) * @param description - Human-readable description for the LLM * @returns Builder with narrowed `TInput` type * * @example * ```typescript * f.mutation('tasks.tag') * .withString('task_id', 'The task to tag') * .withArray('tags', 'string', 'Tags to apply') * .handle(async (input) => { ... }); * // input.tags: string[] ✅ * ``` */ withArray(name: K, itemType: I, description?: string): FluentToolBuilder, TCtx>; /** * Add an optional array parameter. * * @param name - Parameter name * @param itemType - Type of array items (`'string'`, `'number'`, `'boolean'`) * @param description - Human-readable description for the LLM * @returns Builder with narrowed `TInput` type */ withOptionalArray(name: K, itemType: I, description?: string): FluentToolBuilder>, TCtx>; /** * Add context-derivation middleware (tRPC-style). * * Accepts either: * - A `MiddlewareDefinition` from `f.middleware()` (recommended) * - An inline function `({ ctx, next }) => Promise` * * @param mw - Middleware definition or inline function * @returns A **new type** of `FluentToolBuilder` with `TCtx` enriched * * @example * ```typescript * // Option 1: f.middleware() (recommended) * const withAuth = f.middleware(async (ctx) => { * if (ctx.role === 'GUEST') throw error('Unauthorized'); * return { verified: true }; * }); * f.mutation('users.delete') * .use(withAuth) * .handle(async (input, ctx) => { ... }); * * // Option 2: inline * f.mutation('users.delete') * .use(async ({ ctx, next }) => { * const admin = await requireAdmin(ctx.headers); * return next({ ...ctx, adminUser: admin }); * }) * .handle(async (input, ctx) => { ... }); * ``` */ use>(mw: MiddlewareDefinition): FluentToolBuilder; use>(mw: (args: { ctx: TCtx; next: (enrichedCtx: TCtx & TDerived) => Promise; }) => Promise): FluentToolBuilder; /** * Set the MVA Presenter for automatic response formatting. * * When a Presenter is attached, the handler can return raw data * and the framework pipes it through schema validation, system rules, * and UI block generation. * * @param presenter - A Presenter instance * @returns `this` for chaining */ returns(presenter: Presenter): FluentToolBuilder; /** * Add capability tags for selective tool exposure. * * Tags are accumulated — calling `.tags()` multiple times * (or inheriting from a router) appends rather than replaces. * * @param tags - Tag strings for filtering * @returns `this` for chaining */ tags(...tags: string[]): FluentToolBuilder; /** Override: mark this tool as read-only (no side effects) */ readOnly(): FluentToolBuilder; /** Override: mark this tool as destructive (irreversible) */ destructive(): FluentToolBuilder; /** Override: mark this tool as idempotent (safe to retry) */ idempotent(): FluentToolBuilder; /** * Enable TOON-formatted descriptions for token optimization. * * @returns `this` for chaining */ toonDescription(): FluentToolBuilder; /** * Set MCP tool annotations. * * @param a - Annotation key-value pairs * @returns `this` for chaining */ annotations(a: Record): FluentToolBuilder; /** * Declare glob patterns invalidated when this tool succeeds. * * @param patterns - Glob patterns (e.g. `'sprints.*'`, `'tasks.*'`) * @returns `this` for chaining */ invalidates(...patterns: string[]): FluentToolBuilder; /** * Mark this tool's data as immutable (safe to cache forever). * * @returns `this` for chaining */ cached(): FluentToolBuilder; /** * Mark this tool's data as volatile (never cache). * * @returns `this` for chaining */ stale(): FluentToolBuilder; /** * Set concurrency limits for this tool (Semaphore + Queue pattern). * * @param config - Concurrency configuration * @returns `this` for chaining */ concurrency(config: ConcurrencyConfig): FluentToolBuilder; /** * Set maximum payload size for tool responses (Egress Guard). * * @param bytes - Maximum payload size in bytes * @returns `this` for chaining */ egress(bytes: number): FluentToolBuilder; /** * Enable zero-trust sandboxed execution for this tool. * * When enabled: * 1. A `SandboxEngine` is lazily created on the `GroupedToolBuilder` * 2. A system instruction is auto-injected into the tool description * (HATEOAS auto-prompting) so the LLM knows to send JS functions * 3. The handler can use `SandboxEngine.execute()` to run LLM code * in a sealed V8 isolate (no process, require, fs, network) * * @param config - Optional sandbox configuration (timeout, memory, output size) * @returns `this` for chaining * * @example * ```typescript * f.query('data.compute') * .describe('Run safe computation on server data') * .sandboxed({ timeout: 3000, memoryLimit: 64 }) * .withString('expression', 'JS arrow function: (data) => result') * .handle(async (input, ctx) => { * const data = await ctx.db.records.findMany(); * const engine = new SandboxEngine({ timeout: 3000 }); * const result = await engine.execute(input.expression, data); * return result.ok ? result.value : { error: result.error }; * }); * ``` */ sandboxed(config?: SandboxConfig): FluentToolBuilder; /** * Bind this tool to specific FSM states. * * When a `StateMachineGate` is configured on the server, this tool * will only appear in `tools/list` when the FSM is in one of the * specified states. The LLM physically cannot call tools that * don't exist in its reality. * * @param states - FSM state(s) where this tool is visible * @param transition - Event to send to the FSM on successful execution * @returns `this` for chaining * * @example * ```typescript * // Visible only in 'has_items' state, sends CHECKOUT on success * const checkout = f.mutation('cart.checkout') * .bindState('has_items', 'CHECKOUT') * .handle(async (input, ctx) => { ... }); * * // Visible in multiple states * const addItem = f.mutation('cart.add_item') * .bindState(['empty', 'has_items'], 'ADD_ITEM') * .handle(async (input, ctx) => { ... }); * ``` */ bindState(states: string | string[], transition?: string): FluentToolBuilder; /** * Set the handler and build the tool — the terminal step. * * The handler receives `(input, ctx)` with fully typed `TInput` and `TCtx`. * **Implicit `success()` wrapping**: if the handler returns raw data * (not a `ToolResponse`), the framework wraps it with `success()`. * * @param handler - Async function receiving typed `(input, ctx)` * @returns A `GroupedToolBuilder` ready for registration * * @example * ```typescript * const getProject = f.query('projects.get') * .describe('Get a project by ID') * .withString('project_id', 'The exact project ID') * .handle(async (input, ctx) => { * return await ctx.db.projects.findUnique({ where: { id: input.project_id } }); * }); * ``` */ handle(handler: (input: TInput extends void ? Record : TInput, ctx: TCtx) => Promise): GroupedToolBuilder; /** * Alias for `.handle()` — for backward compatibility. * @internal */ resolve(handler: (args: { input: TInput extends void ? Record : TInput; ctx: TCtx; }) => Promise): GroupedToolBuilder; /** @internal */ private _build; } //# sourceMappingURL=FluentToolBuilder.d.ts.map