/** * AgentBase - Core agent class with Hono HTTP server and 5-phase SWML rendering. * * Composes PromptManager, SessionManager, SwmlBuilder, and a tool registry * into a single HTTP-servable agent. */ import { Hono } from 'hono'; import { PromptManager } from './PromptManager.js'; import { PromptObjectModel } from './POM/PromptObjectModel.js'; import { SWMLService } from './SWMLService.js'; import { SwaigFunction, type SwaigHandler } from './SwaigFunction.js'; import { ContextBuilder } from './ContextBuilder.js'; import { SkillManager } from './skills/SkillManager.js'; import type { SkillBase, SkillConfig } from './skills/SkillBase.js'; import { type ServerlessEvent, type ServerlessResponse } from './ServerlessAdapter.js'; import type { AgentOptions, LanguageConfig, PronunciationRule, FunctionInclude, DynamicConfigCallback } from './types.js'; /** * Callback invoked at a registered routing endpoint to determine how to handle an * incoming request. Return a route string to redirect to that agent route, or * null / undefined to let normal SWML processing continue. * * Mirrors Python `web_mixin.register_routing_callback` callback signature (body-only * variant — Hono request object is not forwarded; use the parsed body instead). */ export type RoutingCallback = (body: Record) => string | null | undefined | Promise; /** * Core agent class that composes an HTTP server, prompt management, session handling, * SWAIG tool registry, and 5-phase SWML rendering into a single deployable unit. * * A single `AgentBase` is one HTTP-servable voice agent: * * - `GET /` returns the SWML call-flow document * - `POST /swaig` dispatches SWAIG function calls to registered tool handlers * - `POST /post_prompt` receives the end-of-call summary and invokes {@link onSummary} * * Most user agents either (a) subclass `AgentBase` and override `defineTools()` / `onSummary()` * or (b) use one of the {@link ./prefabs/index.js | prefab agents} (e.g. `ReceptionistAgent`). * * @example Subclass with a custom tool * ```ts * import { AgentBase, FunctionResult } from '@signalwire/sdk'; * * class WeatherAgent extends AgentBase { * static override PROMPT_SECTIONS = [ * { title: 'Role', body: 'You are a weather assistant.' }, * ]; * * protected override defineTools(): void { * this.defineTool({ * name: 'get_forecast', * description: 'Return the forecast for a city.', * parameters: { * type: 'object', * properties: { city: { type: 'string' } }, * required: ['city'], * }, * handler: async ({ city }) => { * const forecast = await fetchForecast(city as string); * return new FunctionResult(forecast); * }, * }); * } * } * * const agent = new WeatherAgent({ name: 'weather', route: '/' }); * await agent.serve({ port: 3000 }); * ``` * * @example Imperative usage (no subclass) * ```ts * const agent = new AgentBase({ name: 'hello', route: '/' }); * agent.setPromptText('You are a friendly greeter.'); * agent.defineTool({ * name: 'say_hi', * description: 'Respond with a greeting.', * parameters: { type: 'object', properties: {} }, * handler: () => new FunctionResult('Hello from SignalWire!'), * }); * await agent.serve(); * ``` * * @see {@link FunctionResult} — builder for tool handler responses * @see {@link ContextBuilder} — multi-step conversation state machines * @see {@link DataMap} — server-side tools without webhooks * @see {@link AgentServer} — host multiple agents on one HTTP server */ export declare class AgentBase extends SWMLService { /** Unique identifier for this agent instance. */ agentId: string; private _promptManager; private sessionManager; private basicAuthCreds; private basicAuthSource; private autoAnswer; private _recordCall; private recordFormat; private recordStereo; private defaultWebhookUrl; private _nativeFunctions; private hints; private languages; private pronounce; private params; private globalData; private functionIncludes; private promptLlmParams; private postPromptLlmParams; private internalFillers; private preAnswerVerbs; private answerConfig; private postAnswerVerbs; private postAiVerbs; private dynamicConfigCallback; private swaigQueryParams; private webHookUrlOverride; private postPromptUrlOverride; private contextsBuilder; private _mcpServers; private _mcpServerEnabled; private _sipRoutingEnabled; private _sipRoute; private _sipAutoMap; private _sipUsernames; private debugEventsEnabled; private debugEventsLevel; private _proxyDebug; private _trustProxyHeaders; private _enforceHttps; /** Structured logger instance for this agent. Override the inherited * SWMLService logger with an AgentBase-tagged one. */ log: import("./Logger.js").Logger; private _skillManager; private _enablePostPromptOverride; private _checkForInputOverride; private _schemaValidation; private _configFile; private _schemaPath; private _appBuiltByAgent; /** * Create a new AgentBase instance. * @param opts - Agent configuration options including name, route, auth, and call settings. */ constructor(opts: AgentOptions); /** * Static prompt sections: subclasses can define these declaratively. * Each entry is applied via promptAddSection() in the constructor. */ static PROMPT_SECTIONS?: { title: string; body?: string; bullets?: string[]; numbered?: boolean; }[]; /** * Lifecycle method to register tools. Subclasses should call this at the * end of their own constructor (after all fields are initialized). * Not called automatically — call `this.defineTools()` explicitly. */ protected defineTools(): void; /** * Public access to the list of registered tools. * * In Python, `define_tools()` is public and returns `List[SWAIGFunction]`. * In TypeScript, `defineTools()` is a protected setup hook (void). This * method provides the equivalent public "get all tools" capability. * @returns Array of all registered SwaigFunction instances. */ getTools(): SwaigFunction[]; /** * Public accessor for the PromptManager (POM). * * Python exposes `self.pom` as a public attribute. This getter * provides equivalent access for direct POM manipulation. */ get promptManager(): PromptManager; /** * Public accessor for the agent's POM as a {@link PromptObjectModel} instance. * * Python parity: ``agent.pom`` instance attribute (agent_base.py line 209) * is a ``signalwire.pom.pom.PromptObjectModel`` when ``use_pom=True``, * or ``None`` otherwise. This getter returns the equivalent TypeScript * ``PromptObjectModel`` instance — callers can use ``addSection``, * ``findSection``, ``renderMarkdown``, ``renderXml``, ``toJson``, ``toYaml`` * exactly as in Python. * * The instance returned is a fresh snapshot built from the current * ``PomBuilder`` state, so mutating it does not feed back into the agent's * internal builder. To mutate the agent's prompt, use * ``promptAddSection`` / ``promptAddToSection`` / ``promptAddSubsection``. * * @returns A `PromptObjectModel` populated with the agent's sections, or `null` when POM mode is off. */ get pom(): PromptObjectModel | null; /** * Public accessor for the native functions list. * * Python exposes `self.native_functions` as a public read/write attribute. * @returns A copy of the native functions list. */ get nativeFunctions(): string[]; /** Set the native functions list. */ set nativeFunctions(fns: string[]); /** * Public read-only accessor for the SkillManager. * * Python exposes `self.skill_manager` as a public attribute. This getter * provides equivalent read access. */ get skillManager(): SkillManager; /** * Set the main system prompt text for the AI. * @param text - The prompt text to use. * @returns This agent instance for chaining. */ setPromptText(text: string): this; /** * Set the post-prompt text evaluated after the call ends. * @param text - The post-prompt text to use. * @returns This agent instance for chaining. */ setPostPrompt(text: string): this; /** * Add a new section to the prompt with optional body, bullets, and subsections. * @param title - Section heading. * @param opts - Optional section content including body text, bullet points, and subsections. * @returns This agent instance for chaining. */ promptAddSection(title: string, opts?: { body?: string; bullets?: string[]; numbered?: boolean; numberedBullets?: boolean; subsections?: { title: string; body?: string; bullets?: string[]; }[]; }): this; /** * Append content to an existing prompt section. * @param title - Title of the section to append to. * @param opts - Content to add: body text, a single bullet, or multiple bullets. * @returns This agent instance for chaining. */ promptAddToSection(title: string, opts?: { body?: string; bullet?: string; bullets?: string[]; }): this; /** * Add a subsection under an existing prompt section. * @param parentTitle - Title of the parent section. * @param title - Title of the new subsection. * @param opts - Optional body text and bullet points for the subsection. * @returns This agent instance for chaining. */ promptAddSubsection(parentTitle: string, title: string, opts?: { body?: string; bullets?: string[]; }): this; /** * Check whether a prompt section with the given title exists. * @param title - Section title to look for. * @returns True if the section exists. */ promptHasSection(title: string): boolean; /** * Get the fully rendered main prompt text. * @returns The assembled prompt string. */ getPrompt(): string; /** * Get the raw POM (Prompt Object Model) structure as an array of section data objects, * when the agent is in POM mode and has at least one section. * * Matches Python `get_prompt()` which returns `Union[str, List[Dict]]` — a raw list when * in POM mode (via `pom.to_list()` / `pom.render_dict()`), or a string otherwise. * The TS `getPrompt()` always returns a string (rendered Markdown), so this companion * method exposes the raw POM structure for callers that need it for serialisation or * inspection (e.g. skills that inspect prompt sections). * * @returns An array of POM section data objects, or null if not in POM mode or POM is empty. */ getPromptPom(): Record[] | null; /** * Get the post-prompt text, if one has been set. * @returns The post-prompt string, or null if not configured. */ getPostPrompt(): string | null; /** * Get the raw prompt text whatever `setPromptText` stored, or null when * no raw prompt has been set. * * Matches Python `PromptManager.get_raw_prompt()` which returns the raw * stored string or `None`. Use this instead of `getPrompt()` when you * need the unrendered text rather than the POM-rendered Markdown. * * @returns The raw prompt string, or null if not set. */ getRawPrompt(): string | null; /** * Set the prompt as a POM (Prompt Object Model) dictionary. * * Replaces the current POM sections with the provided structured data. * Each entry should have `title`, and optionally `body`, `bullets`, * `numbered`, `numberedBullets`, and `subsections`. * * @param pom - Array of POM section dictionaries. * @returns This agent instance for chaining. * @throws Error if POM mode is not enabled (`usePom: false`). */ setPromptPom(pom: Record[]): this; /** * Define or replace the contexts configuration for the AI verb. * @param contexts - An existing ContextBuilder instance or a plain object; a new ContextBuilder is created if omitted. * @returns The active ContextBuilder for further configuration. */ defineContexts(contexts?: ContextBuilder | Record): ContextBuilder; /** * Remove all contexts, returning the agent to a no-contexts state. * * This is a convenience wrapper around `defineContexts().reset()`. * Use it in a dynamic config callback when you need to rebuild * contexts from scratch for a specific request. * * @returns This agent instance for chaining. */ resetContexts(): this; /** * Get the contexts dictionary as serialised SWML, or null when no * contexts have been defined yet. * * Matches Python `PromptManager.get_contexts()` which returns the * contexts dict or `None`. * * @returns Contexts dict, or null when no contexts are defined. */ getContexts(): Record | null; /** * Add a single speech-recognition hint. * @param hint - Word or phrase to boost in speech recognition. * @returns This agent instance for chaining. */ addHint(hint: string): this; /** * Add multiple speech-recognition hints at once. * @param hints - Array of words or phrases to boost. * @returns This agent instance for chaining. */ addHints(hints: string[]): this; /** * Add a pattern-based speech-recognition hint with find-and-replace behavior. * @param opts - Pattern hint configuration with a descriptive hint label, * regex pattern, replacement string, and optional case-insensitive flag. * @returns This agent instance for chaining. */ addPatternHint(opts: { hint: string; pattern: string; replace: string; ignoreCase?: boolean; }): this; /** * Add a supported language to the AI configuration. * @param config - Language configuration including name, code, voice, and optional fillers. * @returns This agent instance for chaining. */ addLanguage(config: LanguageConfig): this; /** * Replace all configured languages with a new list. * @param languages - Array of language configurations. * @returns This agent instance for chaining. */ setLanguages(languages: LanguageConfig[]): this; /** * Add a pronunciation override rule for the TTS engine. * @param rule - Pronunciation rule specifying the text to replace and its substitute. * @returns This agent instance for chaining. */ addPronunciation(rule: PronunciationRule): this; /** * Replace all pronunciation rules with a new list. * @param rules - Array of pronunciation rule objects. * @returns This agent instance for chaining. */ setPronunciations(rules: PronunciationRule[]): this; /** * Set a single AI parameter (e.g. "temperature", "top_p"). * @param key - Parameter name. * @param value - Parameter value. * @returns This agent instance for chaining. */ setParam(key: string, value: unknown): this; /** * Merge multiple AI parameters into the existing params object. * @param params - Key-value pairs to merge. * @returns This agent instance for chaining. */ setParams(params: Record): this; /** * Merge data into the global_data object passed into the AI configuration. * * Matches Python `set_global_data` which calls `.update()` on the internal dict — * existing keys are preserved; incoming keys overwrite on collision. Skills and * other callers can each contribute keys without clobbering one another. * * If you need to replace the entire object, assign a new agent instance or use * `Object.assign(agent.globalData, {})` to clear first. * * @param data - Key-value pairs to merge into global data. * @returns This agent instance for chaining. */ setGlobalData(data: Record): this; /** * Merge additional entries into the existing global_data object. * @param data - Key-value pairs to merge into global data. * @returns This agent instance for chaining. */ updateGlobalData(data: Record): this; /** * Set the list of native SWAIG function names (built-in platform functions). * @param funcs - Array of native function names. * @returns This agent instance for chaining. */ setNativeFunctions(funcs: string[]): this; /** * The complete set of internal SWAIG function names that accept fillers, * matching the SWAIGInternalFiller schema definition. Any name outside * this set is silently ignored by the runtime — addInternalFiller / * setInternalFillers warn if you pass an unknown name. */ static readonly SUPPORTED_INTERNAL_FILLER_NAMES: ReadonlySet; /** * Set internal fillers for native SWAIG functions. * * Internal fillers are short phrases the AI agent speaks (via TTS) while * an internal/native function is running, so the caller doesn't hear * dead air during transitions or background work. * * Supported function names (matches the SWAIGInternalFiller schema): * * hangup — when the agent is hanging up * check_time — when checking the time * wait_for_user — when waiting for user input * wait_seconds — during deliberate pauses * adjust_response_latency — when adjusting response timing * next_step — transitioning between steps in prompt.contexts * change_context — switching between contexts in prompt.contexts * get_visual_input — processing visual input (enable_vision=true) * get_ideal_strategy — thinking (enable_thinking=true) * * Notably NOT supported: change_step, gather_submit, or arbitrary * user-defined SWAIG function names. The runtime only honors fillers * for the names listed above; everything else is silently ignored at * the SWML level. This method warns at registration time if you pass * an unknown name so you catch the typo early. * * @param internalFillers - Map of function name to language-keyed filler phrases. * @returns This agent instance for chaining. * * @example * agent.setInternalFillers({ * next_step: { * 'en-US': ['Moving to the next step...', 'Great, let us continue...'], * 'es': ['Pasando al siguiente paso...'] * }, * check_time: { * 'en-US': ['Let me check the time...'] * } * }); */ setInternalFillers(internalFillers: Record>): this; /** * Add internal filler phrases for a single internal function and language. * * See {@link setInternalFillers} for the complete list of supported * functionName values and an explanation of what fillers do. * * @param functionName - One of the supported internal function names * (see SUPPORTED_INTERNAL_FILLER_NAMES). Names outside the supported * set log a warning and are ignored by the runtime. * @param languageCode - BCP-47 language code for the fillers (e.g. 'en-US'). * @param fillers - Array of filler phrases. * @returns This agent instance for chaining. */ addInternalFiller(functionName: string, languageCode: string, fillers: string[]): this; /** * Add a remote SWAIG function include reference. * @param url - URL of the remote SWAIG endpoint. * @param functions - Function names available at that endpoint. * @param metaData - Optional metadata to attach to the include. * @returns This agent instance for chaining. */ addFunctionInclude(url: string, functions: string[], metaData?: Record): this; /** * Replace the entire list of function includes. * Each include must have a `url` and `functions` array. * @param includes - Array of function include objects. * @returns This agent instance for chaining. */ setFunctionIncludes(includes: FunctionInclude[]): this; /** * Merge LLM-specific parameters into the main prompt configuration (e.g. model, temperature). * @param params - Key-value LLM parameters to merge. * @returns This agent instance for chaining. */ setPromptLlmParams(params: Record): this; /** * Merge LLM-specific parameters into the post-prompt configuration. * @param params - Key-value LLM parameters to merge. * @returns This agent instance for chaining. */ setPostPromptLlmParams(params: Record): this; /** * Enable debug event webhooks for this agent. * @param level - Debug verbosity level (defaults to 1). * @returns This agent instance for chaining. */ enableDebugEvents(level?: number): this; /** * Enable SIP routing for this agent. * @param autoMap - When true, automatically map SIP usernames to the agent route (defaults to true). * @param path - HTTP path for the SIP routing endpoint (defaults to '/sip'). * @returns This agent instance for chaining. */ enableSipRouting(autoMap?: boolean, path?: string): this; /** * Register a SIP username to route to this agent. * @param username - The SIP username to register. * @returns This agent instance for chaining. */ registerSipUsername(username: string): this; /** * Automatically register common SIP usernames based on this agent's * name and route. Derives cleaned variants (alphanumeric + underscore) * and registers each via `registerSipUsername()`. * * Port of Python's `auto_map_sip_usernames()`: * - Registers a cleaned version of the agent name * - Registers a cleaned version of the route (if different from name) * - For names longer than 3 characters, also registers a vowel-stripped variant * * @returns This agent instance for chaining. */ autoMapSipUsernames(): this; /** * Register a callback at a specific HTTP path that decides how to route an * incoming request. * * When called, the endpoint at `path` will invoke `callback` with the parsed * request body. If `callback` returns a non-empty route string the server * responds with `{ action: "redirect", route }` so the platform can forward the * request to the right agent. If `callback` returns `null` / `undefined` the * agent's own SWML is returned instead (normal processing). * * Mirrors Python `swml_service.register_routing_callback` / * `web_mixin.register_routing_callback`. * * @param callback - Function receiving the parsed request body and returning a * route string to redirect, or null/undefined for normal processing. * @param path - HTTP path where this callback endpoint is registered * (default: '/sip'). * @returns This agent instance for chaining. */ registerRoutingCallback(callback: RoutingCallback, path?: string): this; /** * Extract the SIP username from a request body's call.to field. * @param requestBody - The parsed request body containing call information. * @returns The extracted SIP username, or null if not found. */ static extractSipUsername(requestBody: Record): string | null; /** * Add an external MCP server for tool discovery and invocation. * Tools are discovered via MCP protocol at session start and added to the AI's tool list. * @param url - MCP server HTTP endpoint URL * @param opts - Optional configuration: headers, resources, resourceVars * @returns This agent instance for chaining */ addMcpServer(url: string, opts?: { headers?: Record; resources?: boolean; resourceVars?: Record; }): this; /** * Expose this agent's tools as an MCP server endpoint at /mcp. * Adds a JSON-RPC 2.0 endpoint that MCP clients (Claude Desktop, other agents) can connect to. * @returns This agent instance for chaining */ enableMcpServer(): this; /** Check if MCP server endpoint is enabled. */ isMcpServerEnabled(): boolean; /** Get configured MCP servers (read-only copy). */ getMcpServers(): Record[]; /** Build MCP tool list from registered tools. */ private buildMcpToolList; /** Handle an MCP JSON-RPC 2.0 request. Returns the response object. */ handleMcpRequest(body: Record): Promise>; /** * Register a SWAIG tool (function) that the AI can invoke during a call. * * ## How this becomes a tool the model sees * * A SWAIG function is **exactly the same concept** as a "tool" in * native OpenAI / Anthropic tool calling. On every LLM turn, the SDK * renders each registered SWAIG function into the OpenAI tool schema: * * ```json * { * "type": "function", * "function": { * "name": "your_name_here", * "description": "your description text", * "parameters": { /* your JSON schema *\/ } * } * } * ``` * * That schema goes to the model in the same API call that produces * the next assistant message. The model reads: * * - the **function `description`** to decide WHEN to call this tool * - each **parameter `description`** (inside the JSON schema) to * decide HOW to fill in each argument * * This means **descriptions are prompt engineering**, not developer * comments. A vague description is the #1 cause of "the model has the * right tool but doesn't call it" failures. * * ### Bad vs good descriptions * * ```text * BAD : description: 'Lookup function' * GOOD: description: 'Look up a customer's account details by account * number. Use this BEFORE quoting any account-specific info * (balance, plan, status). Do not use for general product * questions.' * * BAD : parameters: { id: { type: 'string', description: 'the id' } } * GOOD: parameters: { account_number: { type: 'string', description: * 'The customer's 8-digit account number, no dashes or spaces. * Ask the user if they don't provide it.' } } * ``` * * ### Tool count matters * * LLM tool selection accuracy degrades past ~7-8 simultaneously-active * tools per call. Use Step.setFunctions() to partition tools across * steps so only the relevant subset is active at any moment. * * @param opts - Tool definition including name, description, parameter * schema, and handler callback. `description` and per-parameter * `description` strings are LLM-facing prompt engineering. * @returns This agent instance for chaining. */ defineTool(opts: { name: string; description: string; parameters?: Record; handler: SwaigHandler; secure?: boolean; fillers?: Record; waitFile?: string; waitFileLoops?: number; required?: string[]; /** External webhook URL; makes this an externally-hosted tool. */ webhookUrl?: string; /** Additional fields to pass through to the SWAIG function definition (Python `**swaig_fields` equivalent). */ extraFields?: Record; }): this; /** * Register a SWAIG tool with a typed handler that receives named parameters * instead of the standard `(args, rawData)` convention. * * The SDK wraps the handler to unpack the args dict into positional params. * If no `parameters` schema is provided, one is inferred from the handler's * source code (parameter names and default values). * * @param opts - Tool definition with a typed handler function. * @returns This agent instance for chaining. */ defineTypedTool(opts: { name: string; description: string; parameters?: Record; handler: Function; secure?: boolean; fillers?: Record; waitFile?: string; waitFileLoops?: number; required?: string[]; }): this; /** * Validate a tool-call token for the given function. * * Mirrors Python reference `core/mixins/state_mixin.py validate_tool_token`: * 1. Unknown function → `false`. * 2. Registered but non-secure → `true` without consulting SessionManager * (non-secure tools never require a token). * 3. Raw-dict descriptors (e.g. DataMap) are treated as secure, matching * Python's `isinstance(func, dict) → is_secure = True` branch. * 4. Missing token on a secure tool → `false`. * 5. Otherwise delegate to `SessionManager.validateToolToken`. * * Divergences from the Python reference: * - No debug-logging branch: `AgentBase` does not expose an agent-level * debug-mode flag, so the per-call debug telemetry Python emits is * omitted. `SessionManager` still logs its own validation outcomes. * - No token-derived call-id fallback: `SessionManager.debugToken` * truncates the embedded call-id for log safety, so an extracted value * cannot be round-tripped back through `validateToolToken`. The caller * is expected to supply a non-empty `callId`; an empty one is forwarded * unchanged and the underlying validator will reject it. */ validateToolToken(functionName: string, token: string, callId: string): boolean; /** * Mint a per-call SWAIG-function token via the agent's SessionManager. * * Mirrors Python reference `core/mixins/state_mixin.py _create_tool_token`: * delegates to `SessionManager.createToolToken` and returns an empty * string on any failure (Python catches all exceptions and returns ""). */ createToolToken(toolName: string, callId: string): string; /** * Add a SWML verb to execute before the answer phase (phase 1). * @param verbName - Name of the SWML verb (e.g. "play", "record"). * @param config - Verb configuration object. * @returns This agent instance for chaining. */ addPreAnswerVerb(verbName: string, config: Record | number): this; /** * Configure the answer verb (phase 2) with optional settings. * @param config - Optional answer verb configuration. * @returns This agent instance for chaining. */ addAnswerVerb(config?: Record): this; /** * Add a SWML verb to execute after the answer phase but before the AI verb (phase 3). * @param verbName - Name of the SWML verb. * @param config - Verb configuration object. * @returns This agent instance for chaining. */ addPostAnswerVerb(verbName: string, config: Record | number): this; /** * Add a SWML verb to execute after the AI verb (phase 5). * @param verbName - Name of the SWML verb. * @param config - Verb configuration object. * @returns This agent instance for chaining. */ addPostAiVerb(verbName: string, config: Record): this; /** * Remove all pre-answer verbs. * @returns This agent instance for chaining. */ clearPreAnswerVerbs(): this; /** * Remove all post-answer verbs. * @returns This agent instance for chaining. */ clearPostAnswerVerbs(): this; /** * Remove all post-AI verbs. * @returns This agent instance for chaining. */ clearPostAiVerbs(): this; /** * Get the agent's display name. * @returns The agent name string. */ getName(): string; /** * Add a skill to this agent, registering its tools, prompt sections, hints, and global data. * @param skill - The skill instance to add. * @returns This agent instance for chaining. */ addSkill(skill: SkillBase): Promise; /** * Add a skill by its registered name, looking it up in the global SkillRegistry. * * Matches Python's `add_skill(skill_name, params)` which loads skills by string * name via the SkillManager registry. Throws a `ValueError`-equivalent if the * skill name is not found in the registry. * * @param skillName - The name the skill was registered under in the SkillRegistry. * @param params - Optional configuration parameters forwarded to the skill factory. * @returns This agent instance for chaining. * @throws Error if no skill with the given name is registered. */ addSkillByName(skillName: string, params?: SkillConfig): Promise; /** * Remove a previously added skill by its instance ID. * @param instanceId - The unique instance ID of the skill to remove. * @returns True if the skill was found and removed. */ removeSkill(instanceId: string): Promise; /** * List all registered skills with their names, instance IDs, and initialization status. * @returns Array of skill descriptors. */ listSkills(): { name: string; instanceId: string; initialized: boolean; }[]; /** * Check whether a skill with the given name is registered. * @param skillName - The skill name to check. * @returns True if a skill with that name exists. */ hasSkill(skillName: string): boolean; /** * Remove a skill by its name (Python parity). * * Python's `remove_skill(skill_name)` removes by skill name. * The existing `removeSkill(instanceId)` removes by instance ID. * This method provides name-based removal for cross-SDK parity. * * @param skillName - The skill name to remove. * @returns True if a skill with that name was found and removed. */ removeSkillByName(skillName: string): Promise; /** * Set a callback invoked on each SWML request to dynamically modify an ephemeral agent copy. * * The callback receives a clone of this agent — mutations apply only to the current * request, so you can vary prompt, tools, languages, params, or global data per call * without affecting the long-lived agent instance. * * @param cb - Callback receiving `(queryParams, bodyParams, headers, agent)` where * `agent` is the ephemeral `AgentBase` copy to mutate. May be async. * @returns This agent instance for chaining. * * @example * ```ts * agent.setDynamicConfigCallback((query, body, headers, agent) => { * const lang = query.lang ?? 'en'; * if (lang === 'es') { * (agent as AgentBase).setPromptText('Eres un asistente útil.'); * } * }); * ``` */ setDynamicConfigCallback(cb: DynamicConfigCallback): this; /** * Add extra query parameters appended to all SWAIG webhook URLs. * @param params - Key-value pairs to append as query parameters. * @returns This agent instance for chaining. */ addSwaigQueryParams(params: Record): this; /** * Clear all SWAIG query parameters. * @returns This agent instance for chaining. */ clearSwaigQueryParams(): this; /** * Manually set the proxy base URL used for webhook URL generation. * @param url - The external-facing base URL (trailing slashes are stripped). * @returns This agent instance for chaining. */ manualSetProxyUrl(url: string): this; /** * Register a callback function that determines routing based on POST data. * * When a routing callback is registered, an endpoint at the specified path * is created in `getApp()`. The callback receives the request body and returns * Enable debug routes for testing and development. * * This is a backward-compatibility stub matching the Python SDK. * In the TypeScript SDK, debug routes (health, ready, debug_events) * are automatically registered in `getApp()`. * * @returns This agent instance for chaining. */ enableDebugRoutes(): this; private detectProxyFromRequest; /** * Override the default SWAIG webhook URL with a custom one. * @param url - The custom webhook URL. * @returns This agent instance for chaining. */ setWebHookUrl(url: string): this; /** * Override the default post-prompt webhook URL with a custom one. * @param url - The custom post-prompt URL. * @returns This agent instance for chaining. */ setPostPromptUrl(url: string): this; /** * Get the full external URL of this agent, using the proxy base URL if available. * @param includeAuth - Whether to embed basic-auth credentials in the URL (defaults to false). * @returns The fully-qualified URL string. */ getFullUrl(includeAuth?: boolean): string; private insertAuth; private buildWebhookUrl; /** * Lifecycle hook called when a post-prompt summary is received. Override in subclasses. * * Invoked once at the end of a call when the AI has produced a structured summary * (configured via `setPostPrompt()` / `setPostPromptJson()`). Use this hook to persist * call data, notify other systems, or trigger follow-up workflows. * * @param _summary - Parsed summary object (JSON when the post-prompt requests * structured output), or `null` if extraction/parsing failed. * @param _rawData - Full raw post-prompt payload received from the platform, * including call metadata, conversation history, and the summary text. * * @example * ```ts * class MyAgent extends AgentBase { * async onSummary(summary, rawData) { * if (!summary) return; * await db.calls.insert({ * callSid: rawData.call_id, * summary, * endedAt: new Date(), * }); * } * } * ``` */ onSummary(_summary: Record | null, _rawData: Record): void | Promise; /** * Lifecycle hook called when SWML is requested. Default delegates to * {@link onSwmlRequest} and returns its result. Subclasses typically * override `onSwmlRequest` rather than this method. * * Matches Python `WebMixin.on_request(request_data, callback_path)`. The * cross-language API is the two-arg form; the Hono `context` argument is * a TypeScript-side extra preserved for callers that already have it but * is not part of the audited surface. * * @param requestData - The parsed request body. * @param callbackPath - Optional callback path from the request. * @returns Optionally a dict of SWML modifications, or undefined for * default rendering. */ onRequest(requestData?: Record | null, callbackPath?: string | null): Record | void | Promise | void>; /** * Lifecycle hook called on every SWML request before rendering. Override in subclasses. * * May optionally return a modification dict that will be merged into the * rendered SWML document (matching Python's `Optional[dict]` return type). * * Matches Python `on_swml_request(request_data, callback_path, request)` — the third * parameter is the FastAPI `Request` in Python; here it is the raw Hono context object * so that subclasses can access query parameters (`context.req.query()`), raw request * headers (`context.req.raw.headers`), etc. * * @param _rawData - The parsed request body. * @param _callbackPath - Optional callback path from the request. * @param _context - The raw Hono context object (c), providing access to headers and query params. * @returns Optionally a dict of SWML modifications, or void. */ onSwmlRequest(_rawData: Record, _callbackPath?: string, _context?: any): Record | void | Promise | void>; /** * Lifecycle hook called when a debug event webhook is received. Override in subclasses. * @param _event - The debug event payload. */ onDebugEvent(_event: Record): void | Promise; /** * Override to add custom basic-auth validation logic beyond credential matching. * @param _username - The username from the request. * @param _password - The password from the request. * @returns True if the credentials are valid; false to reject the request. */ validateBasicAuth(_username: string, _password: string): boolean | Promise; /** * Hook called before each SWAIG function execution. Override in subclasses. * * **Behavioral note:** In the Python SDK, `on_function_call` IS the dispatcher * — it retrieves and executes the function, returning the result. In TypeScript, * `fn.execute()` is called separately after this hook. However, if this method * returns a non-void value, it is used as the result and the default execution * is skipped, enabling dispatch interception parity with Python. * * @param _name - Name of the function about to execute. * @param _args - Parsed arguments for the function. * @param _rawData - The full raw SWAIG request payload. * @returns Optionally a result dict to short-circuit default execution, * or void/undefined to proceed normally. */ onFunctionCall(_name: string, _args: Record, _rawData: Record): Record | void | Promise | void>; /** * Render the complete SWML document by assembling all 5 phases: pre-answer, answer, * post-answer, AI, and post-AI verbs. * * @param callId - Optional call ID to use for session tokens; auto-generated if omitted. * @param modifications - Optional dict returned from `onSwmlRequest` to merge into the AI * verb config before rendering. Matches Python's `_render_swml(modifications)` semantics: * `global_data` is deep-merged; all other keys override the AI config directly. * @returns The rendered SWML document as a JSON string. */ renderSwml(callId?: string, modifications?: Record): string; private createEphemeralCopy; /** * Get or lazily create the Hono HTTP application with all routes, middleware, auth, and CORS. * @returns The configured Hono application instance. */ getApp(): Hono; /** * Return this agent's Hono app for mounting as a sub-router in an AgentServer. * @returns The Hono application instance. */ asRouter(): Hono; /** * Start the HTTP server and begin listening for requests. * * Uses `@hono/node-server` under the hood. When run in CLI mode * (`SWAIG_CLI_MODE=true`, set automatically by `npx swaig-test`), this is a * no-op so agent config can be inspected without starting a server. * * @param opts - Optional host/port overrides. Defaults to the values provided * in the constructor options or the `PORT` environment variable. * @returns A promise that resolves once the server has begun listening. * * @example * ```ts * const agent = new AgentBase({ name: 'demo', port: 3000 }); * await agent.serve(); * // Or override at runtime: * await agent.serve({ port: 8080, host: '127.0.0.1' }); * ``` */ serve(opts?: { host?: string; port?: number; }): Promise; /** * Alias for {@link serve}. Starts the HTTP server. * @param opts - Optional host and port overrides. * @returns A promise that resolves once the server is running. */ run(opts?: { host?: string; port?: number; }): Promise; /** * Handle a single serverless invocation (AWS Lambda, Google Cloud Functions, Azure Functions, or CGI). * * Matches Python `run(event, context)` when executed in a serverless environment. Python's * `run()` auto-detects the platform via `get_execution_mode()` and dispatches accordingly; * in TypeScript the serverless path is an **explicit** method so that `run()` keeps its * HTTP-server semantics and callers opt in to serverless dispatch deliberately. * * Platform detection follows the same environment-variable heuristics as Python's * `ServerlessMixin`: `AWS_LAMBDA_FUNCTION_NAME` → Lambda, `K_SERVICE` → GCF, * `FUNCTIONS_WORKER_RUNTIME` → Azure, `GATEWAY_INTERFACE` → CGI. * * Usage in a Lambda handler file: * ```ts * export const handler = (event: any, context: any) => agent.runServerless(event, context); * ``` * * @param event - The serverless event payload (Lambda event, GCF request body, etc.). * @param context - The serverless context object (Lambda context, Azure context, etc.). * @param platform - Optional platform override; defaults to 'auto' (environment detection). * @returns The normalized serverless response object. */ runServerless(event: ServerlessEvent, context?: unknown, platform?: 'lambda' | 'gcf' | 'azure' | 'cgi' | 'auto'): Promise; private static _shutdownRegistered; /** * Register process signal handlers for clean Kubernetes/Docker shutdown. * Handles SIGTERM and SIGINT, waits for a timeout, then exits. * @param opts - Optional timeout in milliseconds (default 5000). */ static setupGracefulShutdown(opts?: { timeout?: number; }): void; private findSummary; /** * Get the basic-auth credentials used by this agent. * @param includeSource - When true, a third element indicating the credential source is appended. * @returns A tuple of [username, password] or [username, password, source]. */ getBasicAuthCredentials(includeSource?: false): [string, string]; getBasicAuthCredentials(includeSource: true): [string, string, 'provided' | 'environment' | 'generated']; }