Skip to content

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:

  1. Header normalization -- Converts Azure's WHATWG Headers object (or Map-like / Record-like shapes) into a plain Record<string, string>.
  2. Query parameter extraction -- Supports both URLSearchParams and plain objects.
  3. Body reading -- Handles string bodies, JSON bodies, and the request.text() fallback used by some Azure runtimes.
  4. Path parameters -- Reads from request.params or falls back to context.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)

src/functions/agent.ts
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:

src/functions/agent.ts
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:

agentExecute/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

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

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:

package.json (scripts)
{
  "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:

tsconfig.json
{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "target": "ES2022",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true
  },
  "include": ["src/**/*.ts"]
}

Deploying

Using Azure CLI

# Build
npm run build

# Deploy
func azure functionapp publish my-agent-app --javascript

Using Azure DevOps / GitHub Actions

.github/workflows/deploy.yml
- 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().