Azure Functions¶
The framework provides an azureHttp() adapter for Azure Functions v4 (Node.js programming model). This adapter translates the Azure HttpRequest / InvocationContext signature into the framework's platform-neutral HttpEvent, enabling you to deploy the same agent logic to Azure without any code changes.
How It Works¶
The azureHttp() adapter handles:
- Header normalization -- Converts Azure's WHATWG
Headersobject (or Map-like / Record-like shapes) into a plainRecord<string, string>. - Query parameter extraction -- Supports both
URLSearchParamsand plain objects. - Body reading -- Handles string bodies, JSON bodies, and the
request.text()fallback used by some Azure runtimes. - Path parameters -- Reads from
request.paramsor falls back tocontext.bindingData.
The return value is an HttpResponseInit-compatible object that Azure Functions serializes automatically.
sequenceDiagram
participant AF as Azure Functions
participant Adapter as azureHttp()
participant Handler as createAgentExecuteHandler
participant Agent as Your Agent
AF->>Adapter: HttpRequest, InvocationContext
Adapter->>Handler: HttpEvent
Handler->>Agent: AgentContext
Agent-->>Handler: AgentResult
Handler-->>Adapter: JsonResponse
Adapter-->>AF: HttpResponseInit Entry Point¶
Basic (JSON response)¶
import { app } from "@azure/functions";
import {
createAgentExecuteHandler,
azureHttp,
GeminiClient,
ToolRegistry,
} from "@modernpath/agent-framework";
import { MyAgent } from "../agents/MyAgent";
// Build dependencies once (reused across invocations)
const gemini = new GeminiClient({
apiKey: process.env.GOOGLE_AI_STUDIO_KEY!,
model: process.env.GEMINI_MODEL || "gemini-3-flash-preview",
});
const tools = new ToolRegistry();
const agent = new MyAgent(tools, { gemini });
const handler = createAgentExecuteHandler({
resolveAgent: (agentType) => {
if (agentType === "my-agent") return agent;
throw new Error(`Unknown agentType: ${agentType}`);
},
getUserId: async (event) => {
// Extract user identity from headers / token
return 1;
},
cors: { origin: "*" },
});
// Register the Azure Function
app.http("agentExecute", {
methods: ["POST", "OPTIONS"],
authLevel: "anonymous",
route: "agents/{auditingId}/execute",
handler: azureHttp(handler),
});
Using the Convenience Factory¶
The framework also provides createAzureAgentExecuteEndpoint, which composes the handler and adapter in a single call:
import { app } from "@azure/functions";
import {
createAzureAgentExecuteEndpoint,
} from "@modernpath/agent-framework";
// ... (build agent, tools, gemini)
const endpoint = createAzureAgentExecuteEndpoint({
resolveAgent: (agentType) => {
if (agentType === "my-agent") return agent;
throw new Error(`Unknown agentType: ${agentType}`);
},
getUserId: async () => 1,
cors: { origin: "*" },
});
app.http("agentExecute", {
methods: ["POST", "OPTIONS"],
authLevel: "anonymous",
route: "agents/{auditingId}/execute",
handler: endpoint,
});
Function Configuration¶
Azure Functions v4 (Node.js programming model)¶
With the v4 programming model, route bindings are defined in code (not in function.json). The example above uses app.http() to register the function.
If you are using the older v3 programming model with function.json:
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["post", "options"],
"route": "agents/{auditingId}/execute"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
Prefer the v4 programming model
The v4 model is the current standard for Azure Functions with Node.js. It simplifies configuration by moving bindings into TypeScript code and supports ESM modules natively.
Project Structure¶
A typical Azure Functions project using the framework:
my-agent-azure/
src/
functions/
agent.ts # Function registration with app.http()
health.ts # Health check function
agents/
MyAgent.ts # Your agent implementation
config/
env.ts # Environment variable helpers
prompts/
system.yaml # Prompt templates
host.json
local.settings.json # Local development settings
package.json
tsconfig.json
host.json¶
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}
local.settings.json¶
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"GOOGLE_AI_STUDIO_KEY": "your-api-key",
"GEMINI_MODEL": "gemini-3-flash-preview"
}
}
Do not commit local.settings.json
This file contains secrets. Add it to .gitignore.
Bundling Considerations¶
Azure Functions v4 with Node.js supports both TypeScript compilation and esbuild bundling.
Bundle everything into a single file for faster cold starts:
{
"scripts": {
"build": "esbuild src/functions/agent.ts --bundle --platform=node --target=node20 --format=esm --outfile=dist/functions/agent.mjs --external:@google-cloud/firestore --external:@azure/functions"
}
}
External @azure/functions
Mark @azure/functions as external. The Azure runtime provides it automatically, and bundling it causes version conflicts.
If you prefer standard TypeScript compilation:
Deploying¶
Using Azure CLI¶
Using Azure DevOps / GitHub Actions¶
- name: Build
run: npm ci && npm run build
- name: Deploy to Azure Functions
uses: Azure/functions-action@v1
with:
app-name: my-agent-app
package: dist
publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}
Environment Variables¶
Configure these in the Azure Portal under Configuration > Application settings, or via local.settings.json for local development.
GOOGLE_AI_STUDIO_KEY=<your-gemini-api-key>
GEMINI_MODEL=gemini-3-flash-preview
GCP_PROJECT_ID=my-project # If using Firestore-backed stores
For production, use Azure Key Vault references for secrets:
GOOGLE_AI_STUDIO_KEY=@Microsoft.KeyVault(SecretUri=https://my-vault.vault.azure.net/secrets/gemini-key/)
See the Environment Variables reference for the complete list.
Streaming Limitations¶
SSE on Azure Functions
Azure Functions does not natively support Server-Sent Events (SSE) streaming in the same way as Cloud Functions or Cloud Run. The createAgentExecuteStreamHandler produces an AsyncIterable<string>, but Azure's HttpResponseInit expects a complete body.
For streaming use cases on Azure, consider:
- Azure Container Apps -- Supports long-running HTTP connections with SSE.
- Azure Web Apps -- Deploy as a full HTTP server (similar to Cloud Run) with complete streaming support.
- Polling pattern -- Use the standard JSON endpoint and poll for status updates.
Troubleshooting¶
CORS preflight fails
Ensure your function handles OPTIONS requests. The createAgentExecuteHandler returns a 204 for OPTIONS automatically, but verify that methods in your function registration includes "OPTIONS".
Path parameters are not resolved
The azureHttp adapter reads path parameters from request.params first, then falls back to context.bindingData. Ensure your route template includes the parameter (e.g., route: "agents/{auditingId}/execute").
Module not found errors after bundling
If using esbuild, make sure @azure/functions is marked as external. If using tsc, verify that moduleResolution is set to "bundler" or "node16" in your tsconfig.json.
Request body is empty
Azure Functions v4 uses request.text() or request.json() to read the body. The azureHttp adapter handles this automatically by trying request.body first and falling back to request.text().