export {}; // treat each example as a module (top-level await)
declare global {
const ServerlessAdapter: typeof import('@signalwire/sdk').ServerlessAdapter;
const AgentBase: typeof import('@signalwire/sdk').AgentBase;
const readStdin: () => Promise<string>; // helper defined elsewhere in the CGI example
}- Overview
- ServerlessAdapter
- AWS Lambda
- Google Cloud Functions
- Azure Functions
- CGI Mode
- Platform Detection
- URL Generation
- CLI Testing
The SignalWire AI Agents TypeScript SDK can be deployed to serverless platforms using the ServerlessAdapter class (src/ServerlessAdapter.ts). The adapter converts platform-specific event formats into standard Request objects, routes them through the Hono application, and returns normalized responses.
Supported platforms:
| Platform | Identifier | Factory Method |
|---|---|---|
| AWS Lambda | lambda |
ServerlessAdapter.createLambdaHandler() |
| Google Cloud Functions | gcf |
ServerlessAdapter.createGcfHandler() |
| Azure Functions | azure |
ServerlessAdapter.createAzureHandler() |
| CGI | cgi |
Manual via handleRequest() |
import { ServerlessAdapter } from '@signalwire/sdk';
// Auto-detect platform from environment variables
const adapter = new ServerlessAdapter();
// or: new ServerlessAdapter('auto')
// Explicit platform selection
const lambdaAdapter = new ServerlessAdapter('lambda');
const gcfAdapter = new ServerlessAdapter('gcf');
const azureAdapter = new ServerlessAdapter('azure');
const cgiAdapter = new ServerlessAdapter('cgi');The platform parameter accepts 'lambda' | 'gcf' | 'azure' | 'cgi' | 'auto'. When set to 'auto' (the default), the platform is detected from environment variables (see Platform Detection).
ServerlessEvent -- Normalized incoming event:
interface ServerlessEvent {
httpMethod?: string; // HTTP method (AWS Lambda style)
method?: string; // HTTP method (GCF/Azure style)
headers?: Record<string, string>; // Request headers
body?: string | Record<string, unknown>; // Raw or parsed request body
path?: string; // Request path
rawPath?: string; // Raw path (AWS API Gateway v2)
queryStringParameters?: Record<string, string>; // Query parameters
requestContext?: Record<string, unknown>; // Platform-specific context
}ServerlessResponse -- Normalized outgoing response:
interface ServerlessResponse {
statusCode: number; // HTTP status code
headers: Record<string, string>; // Response headers
body: string; // Response body as string
}The core method that processes any serverless event through a Hono app:
const adapter = new ServerlessAdapter('lambda');
const agent = new AgentBase({ name: 'my-agent' });
const app = agent.getApp();
const event: any = {}; // the platform-specific event passed to your handler
const response = await adapter.handleRequest(app, event);
// response: { statusCode: 200, headers: {...}, body: '...' }Processing steps:
- Extract HTTP method from
event.httpMethod(Lambda) orevent.method(GCF/Azure), defaulting toPOST. - Extract path from
event.rawPath(API Gateway v2) orevent.path, defaulting to/. - Build a full URL from the
hostandx-forwarded-protoheaders. - Append query string parameters if present.
- Create a standard
Requestobject and route it throughapp.fetch(). - Convert the
Responseback into aServerlessResponse.
Use ServerlessAdapter.createLambdaHandler() to create a Lambda-compatible handler function.
// handler.ts
import { AgentBase, ServerlessAdapter } from '@signalwire/sdk';
const agent = new AgentBase({
name: 'lambda-agent',
basicAuth: ['admin', process.env.AGENT_PASSWORD!],
});
agent.setPromptText('You are a helpful assistant deployed on AWS Lambda.');
agent.defineTool({
name: 'get_status',
description: 'Get the current system status',
parameters: {
type: 'object',
properties: {
system: { type: 'string', description: 'System name' },
},
},
handler: async (args) => {
const { FunctionResult } = await import('@signalwire/sdk');
const result = new FunctionResult();
result.setResponse(`System ${args.system} is operational.`);
return result;
},
});
const app = agent.getApp();
// Export the Lambda handler
export const handler = ServerlessAdapter.createLambdaHandler(app);createLambdaHandler() creates a new ServerlessAdapter instance with platform: 'lambda' and returns a function with the signature:
(event: ServerlessEvent) => Promise<ServerlessResponse>This matches the AWS Lambda handler contract. The event object maps directly to API Gateway proxy integration events (both v1 and v2):
event.httpMethod-- HTTP method (v1)event.rawPath-- Request path (v2, preferred)event.path-- Request path (v1 fallback)event.headers-- Request headersevent.body-- Request body (string)event.queryStringParameters-- Query parameters
# Build and package
npm run build
zip -r function.zip dist/ node_modules/ package.json
# Deploy via AWS CLI
aws lambda create-function \
--function-name my-agent \
--runtime nodejs20.x \
--handler dist/handler.handler \
--zip-file fileb://function.zip \
--environment "Variables={AGENT_PASSWORD=s3cret}"Use ServerlessAdapter.createGcfHandler() to create a GCF-compatible handler.
// index.ts
import { AgentBase, ServerlessAdapter } from '@signalwire/sdk';
const agent = new AgentBase({
name: 'gcf-agent',
basicAuth: ['admin', process.env.AGENT_PASSWORD!],
});
agent.setPromptText('You are a helpful assistant on Google Cloud Functions.');
const app = agent.getApp();
// Export the GCF handler
export const agentHandler = ServerlessAdapter.createGcfHandler(app);createGcfHandler() returns a function with the signature:
(req: any, res: any) => Promise<void>This matches the Google Cloud Functions HTTP function contract. The adapter:
- Constructs a
ServerlessEventfrom the GCFreqobject:req.method-- HTTP methodreq.headers-- Request headersreq.body-- Request body (already parsed by GCF)req.pathorreq.url-- Request path
- Routes through the Hono app via
handleRequest(). - Writes the response to
resusingres.status(),res.set(), andres.send().
# Deploy via gcloud CLI
gcloud functions deploy agentHandler \
--runtime nodejs20 \
--trigger-http \
--entry-point agentHandler \
--set-env-vars AGENT_PASSWORD=s3cret \
--allow-unauthenticatedUse ServerlessAdapter.createAzureHandler() to create an Azure Functions-compatible handler.
// index.ts
import { AgentBase, ServerlessAdapter } from '@signalwire/sdk';
const agent = new AgentBase({
name: 'azure-agent',
basicAuth: ['admin', process.env.AGENT_PASSWORD!],
});
agent.setPromptText('You are a helpful assistant on Azure Functions.');
const app = agent.getApp();
// Export the Azure handler
const azureHandler = ServerlessAdapter.createAzureHandler(app);
export default azureHandler;createAzureHandler() returns a function with the signature:
(context: any, req: any) => Promise<void>This matches the Azure Functions HTTP trigger contract. The adapter:
- Constructs a
ServerlessEventfrom the Azurereqobject:req.method-- HTTP methodreq.headers-- Request headersreq.body-- Request bodyreq.url-- Request path
- Routes through the Hono app via
handleRequest(). - Sets
context.reswithstatus,headers, andbody.
# Deploy via Azure CLI
func azure functionapp publish my-agent-app \
--typescript{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get", "post"]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}For traditional CGI environments, use the ServerlessAdapter with platform: 'cgi':
import { AgentBase, ServerlessAdapter } from '@signalwire/sdk';
const agent = new AgentBase({ name: 'cgi-agent' });
agent.setPromptText('You are a CGI-deployed assistant.');
const adapter = new ServerlessAdapter('cgi');
const app = agent.getApp();
// Read CGI environment and stdin to build a ServerlessEvent
const event = {
method: process.env.REQUEST_METHOD ?? 'GET',
headers: {
'content-type': process.env.CONTENT_TYPE ?? 'application/json',
host: process.env.HTTP_HOST ?? 'localhost',
authorization: process.env.HTTP_AUTHORIZATION ?? '',
},
path: process.env.PATH_INFO ?? '/',
body: await readStdin(),
};
const response = await adapter.handleRequest(app, event);
// Write CGI response
process.stdout.write(`Status: ${response.statusCode}\r\n`);
for (const [key, value] of Object.entries(response.headers)) {
process.stdout.write(`${key}: ${value}\r\n`);
}
process.stdout.write('\r\n');
process.stdout.write(response.body);CGI mode is auto-detected when the GATEWAY_INTERFACE environment variable is present (e.g., GATEWAY_INTERFACE=CGI/1.1).
When the ServerlessAdapter is created with platform: 'auto' (the default), the platform is detected by checking for well-known environment variables in the following order:
| Order | Environment Variables Checked | Detected Platform |
|---|---|---|
| 1 | AWS_LAMBDA_FUNCTION_NAME or _HANDLER |
lambda |
| 2 | FUNCTION_TARGET or K_SERVICE |
gcf |
| 3 | FUNCTIONS_WORKER_RUNTIME or AZURE_FUNCTIONS_ENVIRONMENT |
azure |
| 4 | GATEWAY_INTERFACE |
cgi |
| 5 | (none matched) | lambda (default fallback) |
const adapter = new ServerlessAdapter('auto');
console.log(adapter.getPlatform());
// 'lambda', 'gcf', 'azure', or 'cgi'The detectPlatform() method can also be called explicitly:
const adapter = new ServerlessAdapter('auto');
const platform = adapter.detectPlatform();The generateUrl() method constructs the expected invocation URL for a deployed function on each platform:
const adapter = new ServerlessAdapter('lambda');
const url = adapter.generateUrl({
region: 'us-west-2',
apiId: 'abc123xyz',
stage: 'prod',
});
// "https://abc123xyz.execute-api.us-west-2.amazonaws.com/prod"AWS Lambda (API Gateway)
const adapter = new ServerlessAdapter('lambda');
adapter.generateUrl({
region: 'us-east-1', // default: AWS_REGION env or 'us-east-1'
apiId: 'abc123', // default: 'API_ID'
stage: 'prod', // default: 'prod'
});
// "https://abc123.execute-api.us-east-1.amazonaws.com/prod"Google Cloud Functions
const adapter = new ServerlessAdapter('gcf');
adapter.generateUrl({
projectId: 'my-project', // default: GCLOUD_PROJECT env or 'PROJECT'
region: 'us-central1', // default: FUNCTION_REGION env or 'us-central1'
functionName: 'agent', // default: AWS_LAMBDA_FUNCTION_NAME env or 'agent'
});
// "https://us-central1-my-project.cloudfunctions.net/agent"Azure Functions
const adapter = new ServerlessAdapter('azure');
adapter.generateUrl({
functionName: 'my-agent', // default: 'agent'
});
// "https://my-agent.azurewebsites.net/api/my-agent"CGI
const adapter = new ServerlessAdapter('cgi');
adapter.generateUrl({
functionName: 'my-agent',
});
// "http://localhost/cgi-bin/my-agent"| Option | Type | Default | Description |
|---|---|---|---|
region |
string |
Platform-specific env var or default | Cloud region for the function. |
projectId |
string |
GCLOUD_PROJECT env or "PROJECT" |
GCP project ID (GCF only). |
functionName |
string |
AWS_LAMBDA_FUNCTION_NAME env or "agent" |
Name of the deployed function. |
stage |
string |
"prod" |
API Gateway stage (Lambda only). |
apiId |
string |
"API_ID" |
API Gateway ID (Lambda only). |
You can test serverless deployments locally using the swaig-test CLI tool. While there is no dedicated --simulate-serverless flag, you can test agent functionality locally before deploying:
# List all registered tools
npx tsx src/cli/swaig-test.ts handler.ts --list-tools
# Dump the SWML document the agent generates
npx tsx src/cli/swaig-test.ts handler.ts --dump-swml
# Execute a specific tool with arguments (--arg key=value, repeatable)
npx tsx src/cli/swaig-test.ts handler.ts --exec get_status --arg system=productionFor local development and testing before deploying to a serverless platform, run the agent as a standard HTTP server:
// local-dev.ts
import { AgentBase } from '@signalwire/sdk';
const agent = new AgentBase({
name: 'my-agent',
port: 3000,
});
agent.setPromptText('You are a helpful assistant.');
// In development, run as an HTTP server
if (process.env.NODE_ENV !== 'production') {
agent.serve();
}
// Export the app for serverless deployment
export const app = agent.getApp();You can manually test the ServerlessAdapter with constructed events:
import { AgentBase, ServerlessAdapter } from '@signalwire/sdk';
const agent = new AgentBase({ name: 'test-agent', basicAuth: ['admin', 'test'] });
agent.setPromptText('Hello!');
const adapter = new ServerlessAdapter('lambda');
const app = agent.getApp();
// Simulate a Lambda event
const event = {
httpMethod: 'POST',
path: '/',
headers: {
'content-type': 'application/json',
authorization: 'Basic ' + Buffer.from('admin:test').toString('base64'),
},
body: JSON.stringify({ call_id: 'test-call-123' }),
};
const response = await adapter.handleRequest(app, event);
console.log('Status:', response.statusCode);
console.log('Body:', response.body);