/** * SWMLService - Lightweight HTTP service for non-AI SWML documents. * * Unlike AgentBase (which always produces an AI block), SWMLService generates * pure SWML call-flow documents: IVR menus, voicemail, call recording, etc. * Uses SwmlBuilder for verb methods and Hono for HTTP serving. */ import { Hono } from 'hono'; import { SwmlBuilder } from './SwmlBuilder.js'; import { SchemaUtils } from './SchemaUtils.js'; import { Logger } from './Logger.js'; import { SwaigFunction, type SwaigFunctionOptions } from './SwaigFunction.js'; import { FunctionResult } from './FunctionResult.js'; import type { Server } from 'node:http'; /** * Interface for custom SWML verb handlers. * Mirrors Python SDK's `SWMLVerbHandler` abstract base class. */ export interface SWMLVerbHandler { /** Return the verb name this handler manages. */ getVerbName(): string; /** Validate a verb configuration. Returns [isValid, errorMessages]. */ validateConfig(config: Record): [boolean, string[]]; /** Build a verb configuration from keyword arguments. */ buildConfig(kwargs: Record): Record; } /** * Registry for custom SWML verb handlers. * Mirrors Python SDK's `VerbHandlerRegistry`. */ export declare class VerbHandlerRegistry { private handlers; /** Register a custom verb handler. */ registerHandler(handler: SWMLVerbHandler): void; /** Get the handler for a specific verb, or undefined if none registered. */ getHandler(verbName: string): SWMLVerbHandler | undefined; /** Check whether a handler exists for the given verb. */ hasHandler(verbName: string): boolean; } /** * Unified security configuration. * Mirrors Python SDK's `SecurityConfig` — reads from env vars and optional config file. */ export declare class SecurityConfig { /** Whether SSL is enabled. */ sslEnabled: boolean; /** Filesystem path to the PEM certificate. */ sslCertPath: string | null; /** Filesystem path to the PEM private key. */ sslKeyPath: string | null; /** Domain name for SSL. */ domain: string | null; /** Basic auth username from config, or null. */ basicAuthUser: string | null; /** Basic auth password from config, or null. */ basicAuthPassword: string | null; private sslConfig; constructor(opts?: { configFile?: string; serviceName?: string; }); private loadFromConfigFile; /** Get basic auth credentials from security config, or null if not configured. */ getBasicAuth(): [string, string] | null; /** Validate that SSL cert and key files are present when SSL is enabled. */ validateSslConfig(): [boolean, string | null]; } /** Callback invoked per-request to dynamically build SWML. */ export type OnRequestCallback = (queryParams: Record, bodyParams: Record, headers: Record) => SwmlBuilder | Promise; type RoutingCallback = (requestBody: Record) => string | null | undefined | Promise; /** Configuration options for SWMLService. */ export interface SWMLServiceOptions { /** * Service display name. * Required to match Python SDK where `name` is a positional required parameter. */ name: string; /** HTTP route path (default '/'). */ route?: string; /** Host to bind the web server to (default '0.0.0.0'). */ host?: string; /** Port to bind the web server to (default PORT env var or 3000). */ port?: number; /** Basic auth credentials as [username, password]. */ basicAuth?: [string, string]; /** Path to a JSON Schema file for verb validation. */ schemaPath?: string; /** Path to a security configuration file. */ configFile?: string; /** Enable schema validation (default true). Can also be disabled via SWML_SKIP_SCHEMA_VALIDATION=true. */ schemaValidation?: boolean; } /** * HTTP service that serves non-AI SWML documents built from verb methods. * * Use `SWMLService` when you need a SignalWire call flow but don't need AI — * plain call routing, IVR-style trees, recording workflows, static playback, etc. * For AI-powered voice agents, use {@link AgentBase} instead. * * @example Static greeting that plays a file and hangs up * ```ts * import { SWMLService } from '@signalwire/sdk'; * * const service = new SWMLService({ name: 'greeter', route: '/', port: 3000 }); * service.builder * .answer() * .play({ url: 'https://cdn.example.com/welcome.mp3' }) * .hangup(); * * await service.serve(); * ``` * * @see {@link SwmlBuilder} — the underlying SWML document builder * @see {@link AgentBase} — AI-powered alternative */ export declare class SWMLService { /** Service display name. */ readonly name: string; /** HTTP route path. */ readonly route: string; /** Host the server binds to. */ readonly host: string; /** Port the server binds to. */ readonly port: number; /** Structured logger, exposed for subclass access. Mirrors Python's public `self.log`. */ readonly log: Logger; /** Unified security configuration. Mirrors Python's `self.security`. */ readonly security: SecurityConfig; /** Whether SSL is enabled. Mirrors Python's `self.ssl_enabled`. */ readonly sslEnabled: boolean; /** Domain name for SSL. Mirrors Python's `self.domain`. */ readonly domain: string | undefined; /** Path to SSL certificate. Mirrors Python's `self.ssl_cert_path`. */ readonly sslCertPath: string | undefined; /** Path to SSL private key. Mirrors Python's `self.ssl_key_path`. */ readonly sslKeyPath: string | undefined; /** Schema validation utilities. Mirrors Python's `self.schema_utils`. */ readonly schemaUtils: SchemaUtils; /** Custom verb handler registry. Mirrors Python's `self.verb_registry`. */ readonly verbRegistry: VerbHandlerRegistry; protected swmlBuilder: SwmlBuilder; protected _app: Hono; protected _server: Server | null; protected onRequestCallback?: OnRequestCallback; protected authCredentials?: [string, string]; protected authSource: 'provided' | 'environment' | 'generated'; /** Validate provided basic-auth credentials against the configured ones * using a constant-time comparison. (Python parity: * ``AuthMixin.validate_basic_auth(username, password)``.) */ validateBasicAuth(username: string, password: string): boolean | Promise; private timingSafeEqual; protected _proxyUrlBase: string | null; protected _proxyUrlBaseFromEnv: boolean; protected _routingCallbacks: Map; protected toolRegistry: Map>; constructor(opts: SWMLServiceOptions); /** @deprecated Prefer passing an options object with a required `name`. The no-arg form defaults name to 'swml-service'. */ constructor(opts?: Partial); /** * Define a SWAIG function the AI can call. Tool descriptions and * parameter descriptions are LLM-facing prompt engineering — see * PORTING_GUIDE for guidance on writing them. * * Accepts the full SwaigFunctionOptions surface so AgentBase's richer * call sites (fillers, secure tokens, wait files, extra fields) work * without overriding this method on the subclass. */ defineTool(opts: SwaigFunctionOptions): this; /** Register a SwaigFunction instance or a raw function descriptor (DataMap). */ registerSwaigFunction(fn: SwaigFunction | Record): this; /** * Dispatch a function call to the registered handler. * Returns null when the function isn't registered or has no handler. * Subclasses (AgentBase) override to add session-token validation * and FunctionResult-shape normalization. Return type is wide enough * to accommodate the agent override (which may also return void * shapes for fire-and-forget tool calls). */ onFunctionCall(name: string, args: Record, rawData: Record): FunctionResult | Record | string | Promise | string | void> | void | null | undefined; /** Whether a tool with the given name is registered. */ hasTool(name: string): boolean; /** Whether a SWAIG function with the given name is registered. * (Python parity: ``ToolRegistry.has_function``.) */ hasFunction(name: string): boolean; /** Get a registered SWAIG function entry, or undefined. * (Python parity: ``ToolRegistry.get_function``.) */ getFunction(name: string): SwaigFunction | Record | undefined; /** Snapshot of all registered SWAIG functions keyed by name. * (Python parity: ``ToolRegistry.get_all_functions``.) */ getAllFunctions(): Record>; /** Remove a registered SWAIG function. Returns true when removed, * false when not found. (Python parity: * ``ToolRegistry.remove_function``.) */ removeFunction(name: string): boolean; /** List registered tool names in insertion order (Map preserves it). */ listToolNames(): string[]; /** * Get a summary of all registered tools with their names, descriptions, and * parameter schemas. Lifted from AgentBase so the swaig-test CLI can list * tools on a non-AgentBase SWMLService target (sidecar / standalone SWAIG * host). * * @returns Array of tool descriptors. */ getRegisteredTools(): { name: string; description: string; parameters: Record; }[]; /** * Look up a registered SwaigFunction by name. Lifted from AgentBase so * `swaig-test --exec ` works against a non-AgentBase SWMLService * target. * * @param name - The tool name to search for. * @returns The SwaigFunction instance, or undefined if not found or not a SwaigFunction. */ getTool(name: string): SwaigFunction | undefined; /** * Extension point: invoked between argument parsing and function dispatch * on POST /swaig. Returns [target, shortCircuit]: when shortCircuit is * non-null, it's returned directly without dispatching. AgentBase may * override to add session-token validation or ephemeral dynamic-config. */ protected swaigPreDispatch(_requestData: Record, _funcName: string): [SWMLService, unknown]; /** * Extension point: register additional Hono routes after SWMLService * mounts /health, /ready, /swaig, and the main route. AgentBase uses * this to add /post_prompt, /check_for_input, /debug_events, /mcp. */ protected registerAdditionalRoutes(_app: Hono): void; /** * Check if full JSON Schema validation is enabled. * Mirrors Python's `@property full_validation_enabled`. * @returns True if schema-based verb validation is active. */ get fullValidationEnabled(): boolean; /** * Get the underlying SwmlBuilder for direct manipulation. * @returns The SwmlBuilder instance. */ getBuilder(): SwmlBuilder; /** * Add a verb to the SWML document. * @param name - Verb name (e.g., 'answer', 'play', 'hangup'). * @param config - Verb configuration. * @returns This service for chaining. */ addVerb(name: string, config: unknown): this; /** * Add a new named section to the SWML document. * Mirrors Python's `add_section()`. * @param sectionName - Name of the section to create. * @returns This service for chaining. */ addSection(sectionName: string): this; /** * Add a verb to a specific named section. * Mirrors Python's `add_verb_to_section()`. * @param sectionName - Target section name (auto-created if missing). * @param verbName - Verb name. * @param config - Verb configuration. * @returns This service for chaining. */ addVerbToSection(sectionName: string, verbName: string, config: unknown): this; /** * Reset the SWML document to an empty state. * Mirrors Python's `reset_document()`. * @returns This service for chaining. */ resetDocument(): this; /** * Render the SWML document. * * Subclass-override-friendly signature: AgentBase overrides this with * `(callId?: string, modifications?: Record): string` * to return a serialized JSON string built from prompts + dynamic config. * Plain SWMLService returns the in-memory document object. * * @returns The SWML document (object) or its serialized form (subclass). */ renderSwml(_callId?: string, _modifications?: Record): Record | string; /** * Get the SWML document as a dictionary. * Alias for `renderSwml()` that matches Python's `get_document()` name. * @returns The SWML document. */ getDocument(): Record; /** * Render the SWML document as a JSON string. * Mirrors Python's `render_document()`. * @returns JSON-encoded SWML document. */ renderDocument(): string; /** * Register a custom verb handler. * Mirrors Python's `register_verb_handler()`. * @param handler - The verb handler to register. */ registerVerbHandler(handler: SWMLVerbHandler): void; /** * Register a routing callback at a given path. * When a POST request arrives at `path`, the callback is invoked with the * parsed request body. If it returns a string, the response is a 307 redirect * to that route; if it returns null, normal SWML serving continues. * Mirrors Python's `register_routing_callback()`. * * @param callbackFn - Callback receiving the request body and returning a route or null. * @param path - HTTP path for the callback (default '/sip'). */ registerRoutingCallback(callbackFn: RoutingCallback, path?: string): void; /** * Extract the SIP username from a request body's call.to field. * Mirrors Python's `@staticmethod extract_sip_username()`. * @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; /** * Service-side SWML-builder hook. Subclasses return a `SwmlBuilder` * to fully replace the document for this request, or `null` to fall * through to `setOnRequestCallback` or the static builder. * * This is distinct from the WebMixin `onRequest(requestData, * callbackPath)` hook on AgentBase (which mirrors Python's * `on_request -> on_swml_request` modification-merge contract). * Use this hook when you want to swap the entire SWML builder; use * `onRequest` / `onSwmlRequest` on AgentBase when you want to merge * targeted modifications into the rendered document. * * Default implementation returns `null` (no-op). * * @param queryParams - URL query parameters from the request. * @param bodyParams - Parsed POST body (empty object for GET requests). * @param headers - HTTP request headers. * @param callbackPath - The callback sub-path being handled, if any. * @returns A `SwmlBuilder` whose document is sent as the response, or * `null` to delegate to the next handler in the chain. */ protected buildSwmlForRequest(_queryParams: Record, _bodyParams: Record, _headers: Record, _callbackPath?: string): SwmlBuilder | null; /** * Set a callback invoked per-request to dynamically build SWML. * When set, the static SwmlBuilder is ignored and the callback's * returned SwmlBuilder is used instead. * @param cb - Callback receiving query params, body params, and headers. * @returns This service for chaining. */ setOnRequestCallback(cb: OnRequestCallback): this; /** * Get the basic-auth credentials used by this service. * Mirrors Python's `get_basic_auth_credentials()`. * @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']; /** * Manually set the proxy base URL used for webhook URL generation. * Mirrors Python's `manual_set_proxy_url()`. * @param url - The external-facing base URL (trailing slashes are stripped). * @returns This service for chaining. */ manualSetProxyUrl(url: string): this; /** * Get the Hono application for mounting or testing. * This is the TypeScript equivalent of Python's `as_router()`, which returns * a FastAPI `APIRouter`. Both expose the underlying app/router so callers can * mount it into a larger framework. Use `asRouter()` when porting Python code * that calls `as_router()` directly. * @returns The configured Hono app. */ getApp(): Hono; /** * Alias for `getApp()`. Provided for cross-SDK consistency with Python's * `as_router()` method — allows Python callers porting to TypeScript to use * the familiar name without changes. * @returns The configured Hono app. */ asRouter(): Hono; /** * Start the HTTP server. * * Matches Python's `serve()` parameters including SSL options. When * `SWAIG_CLI_MODE=true` is set in the environment (e.g. while running the * `swaig-test` CLI) the call is a no-op. * * @param host - Hostname. Defaults to `this.host` (constructor value) or * `'0.0.0.0'`. * @param port - Port. Defaults to `this.port` (constructor value) or `3000`. * @param opts - Optional SSL/TLS configuration overrides. * @param opts.sslCert - Path to the PEM certificate file. * @param opts.sslKey - Path to the PEM private key file. * @param opts.sslEnabled - When `true`, serve over HTTPS. * @param opts.domain - Domain used for HSTS header configuration. * @returns Resolves once the server has begun listening. */ run(hostOrOpts?: string | { host?: string; port?: number; sslCert?: string; sslKey?: string; sslEnabled?: boolean; domain?: string; }, port?: number, opts?: { sslCert?: string; sslKey?: string; sslEnabled?: boolean; domain?: string; }): Promise; /** * Start the HTTP server. Alias for {@link run} provided for cross-SDK * consistency with Python's `serve()` method — callers porting from * Python can use this name without changes. * * @param args - Forwarded unchanged to {@link run}. * @returns Resolves once the server has begun listening. */ serve(...args: Parameters): Promise; /** * Stop the HTTP server. * Mirrors Python's `stop()`. */ stop(): void; } export {};