Merge b94b8aea56 into 09be5d45d5
This commit is contained in:
commit
bc2b99f6b8
12
.env.example
12
.env.example
@ -3,3 +3,15 @@ TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|||||||
TWILIO_AUTH_TOKEN=your_auth_token_here
|
TWILIO_AUTH_TOKEN=your_auth_token_here
|
||||||
# Must be a WhatsApp-enabled Twilio number, prefixed with whatsapp:
|
# Must be a WhatsApp-enabled Twilio number, prefixed with whatsapp:
|
||||||
TWILIO_WHATSAPP_FROM=whatsapp:+17343367101
|
TWILIO_WHATSAPP_FROM=whatsapp:+17343367101
|
||||||
|
|
||||||
|
# Azure OpenAI Configuration
|
||||||
|
# Your Azure OpenAI API key
|
||||||
|
AZURE_OPENAI_API_KEY=your_azure_openai_api_key_here
|
||||||
|
# Your Azure OpenAI resource name (e.g., "my-openai-resource")
|
||||||
|
AZURE_OPENAI_RESOURCE_NAME=your_resource_name_here
|
||||||
|
# Your Azure OpenAI deployment name (e.g., "gpt-5", "gpt-5-codex")
|
||||||
|
AZURE_OPENAI_DEPLOYMENT_NAME=your_deployment_name_here
|
||||||
|
# API version (optional, defaults to 2024-08-01-preview)
|
||||||
|
AZURE_OPENAI_API_VERSION=2024-08-01-preview
|
||||||
|
# Or provide the full endpoint URL directly (alternative to resource name)
|
||||||
|
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
|
||||||
|
|||||||
100
docker-compose.azure.yml
Normal file
100
docker-compose.azure.yml
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Docker Compose for Azure OpenAI deployment
|
||||||
|
# Usage:
|
||||||
|
# 1. Copy .env.example to .env and fill in your Azure OpenAI credentials
|
||||||
|
# 2. Build the image: docker compose -f docker-compose.azure.yml build
|
||||||
|
# 3. Run the gateway: docker compose -f docker-compose.azure.yml up -d moltbot-azure-gateway
|
||||||
|
# 4. Or run the CLI: docker compose -f docker-compose.azure.yml run --rm moltbot-azure-cli
|
||||||
|
|
||||||
|
services:
|
||||||
|
moltbot-azure-gateway:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: moltbot-azure:local
|
||||||
|
container_name: moltbot-azure-gateway
|
||||||
|
environment:
|
||||||
|
HOME: /home/node
|
||||||
|
TERM: xterm-256color
|
||||||
|
# Gateway authentication token (generate a secure token for production)
|
||||||
|
CLAWDBOT_GATEWAY_TOKEN: ${CLAWDBOT_GATEWAY_TOKEN:-your-secure-gateway-token}
|
||||||
|
# Azure OpenAI Configuration (required)
|
||||||
|
AZURE_OPENAI_API_KEY: ${AZURE_OPENAI_API_KEY:?Azure OpenAI API key is required}
|
||||||
|
AZURE_OPENAI_RESOURCE_NAME: ${AZURE_OPENAI_RESOURCE_NAME:?Azure OpenAI resource name is required}
|
||||||
|
AZURE_OPENAI_DEPLOYMENT_NAME: ${AZURE_OPENAI_DEPLOYMENT_NAME:?Azure OpenAI deployment name is required}
|
||||||
|
AZURE_OPENAI_API_VERSION: ${AZURE_OPENAI_API_VERSION:-2024-08-01-preview}
|
||||||
|
# Optional: provide full endpoint URL instead of resource name
|
||||||
|
AZURE_OPENAI_ENDPOINT: ${AZURE_OPENAI_ENDPOINT}
|
||||||
|
volumes:
|
||||||
|
# Persist configuration and session data
|
||||||
|
- ${CLAWDBOT_CONFIG_DIR:-./data/config}:/home/node/.moltbot
|
||||||
|
- ${CLAWDBOT_WORKSPACE_DIR:-./data/workspace}:/home/node/clawd
|
||||||
|
ports:
|
||||||
|
# Gateway HTTP/WebSocket port
|
||||||
|
- "${CLAWDBOT_GATEWAY_PORT:-18789}:18789"
|
||||||
|
# Bridge port for channel connections
|
||||||
|
- "${CLAWDBOT_BRIDGE_PORT:-18790}:18790"
|
||||||
|
init: true
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
[
|
||||||
|
"node",
|
||||||
|
"dist/index.js",
|
||||||
|
"gateway",
|
||||||
|
"--bind",
|
||||||
|
"${CLAWDBOT_GATEWAY_BIND:-lan}",
|
||||||
|
"--port",
|
||||||
|
"${CLAWDBOT_GATEWAY_PORT:-18789}",
|
||||||
|
"--allow-unconfigured"
|
||||||
|
]
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:18789/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
moltbot-azure-cli:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: moltbot-azure:local
|
||||||
|
container_name: moltbot-azure-cli
|
||||||
|
environment:
|
||||||
|
HOME: /home/node
|
||||||
|
TERM: xterm-256color
|
||||||
|
BROWSER: echo
|
||||||
|
# Azure OpenAI Configuration (required)
|
||||||
|
AZURE_OPENAI_API_KEY: ${AZURE_OPENAI_API_KEY:?Azure OpenAI API key is required}
|
||||||
|
AZURE_OPENAI_RESOURCE_NAME: ${AZURE_OPENAI_RESOURCE_NAME:?Azure OpenAI resource name is required}
|
||||||
|
AZURE_OPENAI_DEPLOYMENT_NAME: ${AZURE_OPENAI_DEPLOYMENT_NAME:?Azure OpenAI deployment name is required}
|
||||||
|
AZURE_OPENAI_API_VERSION: ${AZURE_OPENAI_API_VERSION:-2024-08-01-preview}
|
||||||
|
AZURE_OPENAI_ENDPOINT: ${AZURE_OPENAI_ENDPOINT}
|
||||||
|
volumes:
|
||||||
|
- ${CLAWDBOT_CONFIG_DIR:-./data/config}:/home/node/.moltbot
|
||||||
|
- ${CLAWDBOT_WORKSPACE_DIR:-./data/workspace}:/home/node/clawd
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
init: true
|
||||||
|
entrypoint: ["node", "dist/index.js"]
|
||||||
|
|
||||||
|
# Minimal test service to verify Azure OpenAI connection
|
||||||
|
moltbot-azure-test:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: moltbot-azure:local
|
||||||
|
environment:
|
||||||
|
HOME: /home/node
|
||||||
|
TERM: xterm-256color
|
||||||
|
AZURE_OPENAI_API_KEY: ${AZURE_OPENAI_API_KEY:?Azure OpenAI API key is required}
|
||||||
|
AZURE_OPENAI_RESOURCE_NAME: ${AZURE_OPENAI_RESOURCE_NAME:?Azure OpenAI resource name is required}
|
||||||
|
AZURE_OPENAI_DEPLOYMENT_NAME: ${AZURE_OPENAI_DEPLOYMENT_NAME:?Azure OpenAI deployment name is required}
|
||||||
|
AZURE_OPENAI_API_VERSION: ${AZURE_OPENAI_API_VERSION:-2024-08-01-preview}
|
||||||
|
command: ["node", "dist/index.js", "models", "list"]
|
||||||
|
profiles:
|
||||||
|
- test
|
||||||
|
|
||||||
|
# Named volumes for persistent storage (optional, use bind mounts above for easier access)
|
||||||
|
volumes:
|
||||||
|
moltbot-config:
|
||||||
|
moltbot-workspace:
|
||||||
150
docs/providers/azure-openai.md
Normal file
150
docs/providers/azure-openai.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# Azure OpenAI Provider
|
||||||
|
|
||||||
|
Configure Moltbot to use Azure OpenAI Service as a model provider.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Azure OpenAI exposes OpenAI models deployed in your Azure subscription (for example `gpt-5` or `gpt-5-codex`) behind Azure endpoints and authentication.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. An Azure subscription
|
||||||
|
2. An Azure OpenAI resource created in Azure Portal
|
||||||
|
3. At least one model deployment (e.g., `gpt-5` or `gpt-5-codex`)
|
||||||
|
4. API key from the Azure OpenAI resource
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Set the following environment variables to configure Azure OpenAI:
|
||||||
|
|
||||||
|
| Variable | Required | Description |
|
||||||
|
|----------|----------|-------------|
|
||||||
|
| `AZURE_OPENAI_API_KEY` | Yes | Your Azure OpenAI API key |
|
||||||
|
| `AZURE_OPENAI_RESOURCE_NAME` | Yes* | Azure OpenAI resource name (e.g., `my-openai-resource`) |
|
||||||
|
| `AZURE_OPENAI_DEPLOYMENT_NAME` | Yes | Model deployment name (e.g., `gpt-5`) |
|
||||||
|
| `AZURE_OPENAI_API_VERSION` | No | API version (defaults to `2024-08-01-preview`) |
|
||||||
|
| `AZURE_OPENAI_ENDPOINT` | No | Full endpoint URL (alternative to resource name) |
|
||||||
|
|
||||||
|
\* Either `AZURE_OPENAI_RESOURCE_NAME` or `AZURE_OPENAI_ENDPOINT` is required.
|
||||||
|
|
||||||
|
### Example .env Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Azure OpenAI Configuration
|
||||||
|
AZURE_OPENAI_API_KEY=your_azure_openai_api_key_here
|
||||||
|
AZURE_OPENAI_RESOURCE_NAME=my-openai-resource
|
||||||
|
AZURE_OPENAI_DEPLOYMENT_NAME=gpt-5
|
||||||
|
AZURE_OPENAI_API_VERSION=2024-08-01-preview
|
||||||
|
|
||||||
|
# Alternative: provide full endpoint URL
|
||||||
|
# AZURE_OPENAI_ENDPOINT=https://my-openai-resource.openai.azure.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker Deployment
|
||||||
|
|
||||||
|
A dedicated Docker Compose file (`docker-compose.azure.yml`) is provided for Azure OpenAI deployments.
|
||||||
|
|
||||||
|
### Quick Start
|
||||||
|
|
||||||
|
1. Copy `.env.example` to `.env` and fill in your Azure OpenAI credentials:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your Azure OpenAI configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build the Docker image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.azure.yml build
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run the gateway:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.azure.yml up -d moltbot-azure-gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Or run the CLI interactively:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.azure.yml run --rm moltbot-azure-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### Services
|
||||||
|
|
||||||
|
The `docker-compose.azure.yml` includes a gateway service, an interactive CLI service, and an optional test profile.
|
||||||
|
|
||||||
|
### Testing Connection
|
||||||
|
|
||||||
|
Verify your Azure OpenAI connection:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.azure.yml --profile test run --rm moltbot-azure-test
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoint Format
|
||||||
|
|
||||||
|
Azure OpenAI uses a different URL format than OpenAI:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://{resourceName}.openai.azure.com/openai/deployments/{deploymentName}/chat/completions?api-version={apiVersion}
|
||||||
|
```
|
||||||
|
|
||||||
|
The provider automatically handles:
|
||||||
|
- Building the correct endpoint URL from resource and deployment names
|
||||||
|
- Adding the `api-version` query parameter via a global fetch wrapper
|
||||||
|
- Using the `api-key` header instead of Bearer token authentication
|
||||||
|
|
||||||
|
## Supported Models
|
||||||
|
|
||||||
|
Moltbot treats Azure deployments as model IDs. Use your deployment name as `AZURE_OPENAI_DEPLOYMENT_NAME`.
|
||||||
|
|
||||||
|
Recommended (newer) deployment names to use in docs/examples:
|
||||||
|
|
||||||
|
- `gpt-5`
|
||||||
|
- `gpt-5-mini`
|
||||||
|
- `gpt-5-nano`
|
||||||
|
- `gpt-5-codex`
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**401 Unauthorized**
|
||||||
|
- Verify your `AZURE_OPENAI_API_KEY` is correct
|
||||||
|
- Check that the API key has access to the specified deployment
|
||||||
|
|
||||||
|
**404 Not Found**
|
||||||
|
- Verify `AZURE_OPENAI_RESOURCE_NAME` matches your Azure resource
|
||||||
|
- Verify `AZURE_OPENAI_DEPLOYMENT_NAME` matches an existing deployment
|
||||||
|
- Check that the deployment is in a "Succeeded" state in Azure Portal
|
||||||
|
|
||||||
|
**API Version Errors**
|
||||||
|
- Try updating `AZURE_OPENAI_API_VERSION` to a supported version
|
||||||
|
- Check [Azure OpenAI API versions](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference) for the latest
|
||||||
|
|
||||||
|
### Verifying Configuration
|
||||||
|
|
||||||
|
List available models to verify your configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
moltbot models list
|
||||||
|
```
|
||||||
|
|
||||||
|
The Azure OpenAI deployment should appear as `azure-openai/{deployment-name}`.
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- Store API keys securely using environment variables or a secrets manager
|
||||||
|
- Consider using Azure Managed Identity for production deployments
|
||||||
|
- Review Azure OpenAI content filtering policies for your use case
|
||||||
|
- Ensure your Azure resource has appropriate network access controls
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Azure OpenAI Service Documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/)
|
||||||
|
- [Azure OpenAI API Reference](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference)
|
||||||
|
- [Moltbot Configuration](/configuration)
|
||||||
342
src/agents/azure-openai-provider.ts
Normal file
342
src/agents/azure-openai-provider.ts
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
/**
|
||||||
|
* Azure OpenAI Provider Configuration
|
||||||
|
*
|
||||||
|
* This module provides support for Azure OpenAI Service, which uses a different
|
||||||
|
* API endpoint format and authentication mechanism compared to OpenAI's standard API.
|
||||||
|
*
|
||||||
|
* Azure OpenAI endpoint format:
|
||||||
|
* https://{resourceName}.openai.azure.com/openai/deployments/{deploymentName}/chat/completions?api-version={apiVersion}
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ModelDefinitionConfig } from "../config/types.models.js";
|
||||||
|
import type { ProviderConfig } from "./models-config.providers.js";
|
||||||
|
|
||||||
|
// Store for Azure OpenAI configurations to enable fetch interception
|
||||||
|
const azureOpenAIConfigs = new Map<string, { apiVersion: string }>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an Azure OpenAI resource for fetch interception.
|
||||||
|
* This enables the global fetch wrapper to add api-version query parameters.
|
||||||
|
*/
|
||||||
|
export function registerAzureOpenAIResource(resourceName: string, apiVersion: string): void {
|
||||||
|
azureOpenAIConfigs.set(resourceName.toLowerCase(), { apiVersion });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a URL is an Azure OpenAI endpoint and get its configuration.
|
||||||
|
*/
|
||||||
|
export function getAzureOpenAIConfig(
|
||||||
|
url: string,
|
||||||
|
): { resourceName: string; apiVersion: string } | null {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const match = /^([^.]+)\.openai\.azure\.com$/.exec(parsed.hostname);
|
||||||
|
if (!match) return null;
|
||||||
|
const resourceName = match[1].toLowerCase();
|
||||||
|
const config = azureOpenAIConfigs.get(resourceName);
|
||||||
|
if (!config) return null;
|
||||||
|
return { resourceName, apiVersion: config.apiVersion };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track if the fetch wrapper has been installed
|
||||||
|
let fetchWrapperInstalled = false;
|
||||||
|
let originalFetch: typeof fetch | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a global fetch wrapper that adds api-version query parameter
|
||||||
|
* to Azure OpenAI requests.
|
||||||
|
*/
|
||||||
|
export function installAzureOpenAIFetchWrapper(): void {
|
||||||
|
if (fetchWrapperInstalled) return;
|
||||||
|
|
||||||
|
originalFetch = globalThis.fetch;
|
||||||
|
fetchWrapperInstalled = true;
|
||||||
|
|
||||||
|
globalThis.fetch = async function azureOpenAIFetchWrapper(
|
||||||
|
input: RequestInfo | URL,
|
||||||
|
init?: RequestInit,
|
||||||
|
): Promise<Response> {
|
||||||
|
const url =
|
||||||
|
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||||
|
const azureConfig = getAzureOpenAIConfig(url);
|
||||||
|
|
||||||
|
if (azureConfig) {
|
||||||
|
// Add api-version query parameter to Azure OpenAI requests
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
if (!parsedUrl.searchParams.has("api-version")) {
|
||||||
|
parsedUrl.searchParams.set("api-version", azureConfig.apiVersion);
|
||||||
|
const newUrl = parsedUrl.toString();
|
||||||
|
|
||||||
|
// Recreate the request with the new URL
|
||||||
|
if (typeof input === "string") {
|
||||||
|
return originalFetch!(newUrl, init);
|
||||||
|
} else if (input instanceof URL) {
|
||||||
|
return originalFetch!(new URL(newUrl), init);
|
||||||
|
} else {
|
||||||
|
// Request object - create new request with modified URL
|
||||||
|
const newRequest = new Request(newUrl, {
|
||||||
|
method: input.method,
|
||||||
|
headers: input.headers,
|
||||||
|
body: init?.body ?? input.body,
|
||||||
|
mode: input.mode,
|
||||||
|
credentials: input.credentials,
|
||||||
|
cache: input.cache,
|
||||||
|
redirect: input.redirect,
|
||||||
|
referrer: input.referrer,
|
||||||
|
integrity: input.integrity,
|
||||||
|
signal: init?.signal ?? input.signal,
|
||||||
|
});
|
||||||
|
return originalFetch!(newRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalFetch!(input, init);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall the Azure OpenAI fetch wrapper (for testing).
|
||||||
|
*/
|
||||||
|
export function uninstallAzureOpenAIFetchWrapper(): void {
|
||||||
|
if (!fetchWrapperInstalled || !originalFetch) return;
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
fetchWrapperInstalled = false;
|
||||||
|
originalFetch = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Azure OpenAI default configuration
|
||||||
|
const AZURE_OPENAI_DEFAULT_API_VERSION = "2024-08-01-preview";
|
||||||
|
const AZURE_OPENAI_DEFAULT_CONTEXT_WINDOW = 128000;
|
||||||
|
const AZURE_OPENAI_DEFAULT_MAX_TOKENS = 4096;
|
||||||
|
const AZURE_OPENAI_DEFAULT_COST = {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface AzureOpenAIConfig {
|
||||||
|
/** Azure OpenAI resource name (the name of your Azure OpenAI resource) */
|
||||||
|
resourceName: string;
|
||||||
|
/** Deployment name (the name of your model deployment) */
|
||||||
|
deploymentName: string;
|
||||||
|
/** API version (defaults to 2024-08-01-preview) */
|
||||||
|
apiVersion?: string;
|
||||||
|
/** Azure OpenAI API key */
|
||||||
|
apiKey?: string;
|
||||||
|
/** Custom model display name */
|
||||||
|
modelName?: string;
|
||||||
|
/** Whether this is a reasoning model (like o1) */
|
||||||
|
reasoning?: boolean;
|
||||||
|
/** Supported input types */
|
||||||
|
input?: Array<"text" | "image">;
|
||||||
|
/** Context window size */
|
||||||
|
contextWindow?: number;
|
||||||
|
/** Max output tokens */
|
||||||
|
maxTokens?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the Azure OpenAI base URL from resource name
|
||||||
|
*/
|
||||||
|
export function buildAzureOpenAIBaseUrl(resourceName: string): string {
|
||||||
|
return `https://${resourceName}.openai.azure.com`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the full Azure OpenAI API endpoint URL
|
||||||
|
*/
|
||||||
|
export function buildAzureOpenAIEndpoint(
|
||||||
|
resourceName: string,
|
||||||
|
deploymentName: string,
|
||||||
|
apiVersion: string = AZURE_OPENAI_DEFAULT_API_VERSION,
|
||||||
|
): string {
|
||||||
|
return `${buildAzureOpenAIBaseUrl(resourceName)}/openai/deployments/${deploymentName}/chat/completions?api-version=${apiVersion}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a model definition for Azure OpenAI deployment
|
||||||
|
*/
|
||||||
|
export function buildAzureOpenAIModelDefinition(config: AzureOpenAIConfig): ModelDefinitionConfig {
|
||||||
|
const modelId = config.deploymentName;
|
||||||
|
const modelName = config.modelName ?? `Azure ${config.deploymentName}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: modelId,
|
||||||
|
name: modelName,
|
||||||
|
reasoning: config.reasoning ?? false,
|
||||||
|
input: config.input ?? ["text"],
|
||||||
|
cost: AZURE_OPENAI_DEFAULT_COST,
|
||||||
|
contextWindow: config.contextWindow ?? AZURE_OPENAI_DEFAULT_CONTEXT_WINDOW,
|
||||||
|
maxTokens: config.maxTokens ?? AZURE_OPENAI_DEFAULT_MAX_TOKENS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a provider configuration for Azure OpenAI
|
||||||
|
*
|
||||||
|
* Azure OpenAI uses a different URL format:
|
||||||
|
* https://{resource}.openai.azure.com/openai/deployments/{deployment}/chat/completions?api-version={version}
|
||||||
|
*
|
||||||
|
* We set the baseUrl to include the deployment path so the OpenAI SDK appends /chat/completions correctly.
|
||||||
|
* The api-version query parameter is added automatically via a global fetch wrapper.
|
||||||
|
*/
|
||||||
|
export function buildAzureOpenAIProvider(config: AzureOpenAIConfig): ProviderConfig {
|
||||||
|
const apiVersion = config.apiVersion ?? AZURE_OPENAI_DEFAULT_API_VERSION;
|
||||||
|
// Azure OpenAI requires the deployment in the URL path
|
||||||
|
// Format: https://{resource}.openai.azure.com/openai/deployments/{deployment}
|
||||||
|
// The SDK will append /chat/completions to this
|
||||||
|
const baseUrl = `https://${config.resourceName}.openai.azure.com/openai/deployments/${config.deploymentName}`;
|
||||||
|
|
||||||
|
// Register the resource for fetch interception to add api-version query param
|
||||||
|
registerAzureOpenAIResource(config.resourceName, apiVersion);
|
||||||
|
// Install the global fetch wrapper
|
||||||
|
installAzureOpenAIFetchWrapper();
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseUrl,
|
||||||
|
api: "openai-completions",
|
||||||
|
apiKey: config.apiKey,
|
||||||
|
// Azure OpenAI uses api-key header instead of Bearer token
|
||||||
|
authHeader: false,
|
||||||
|
headers: {
|
||||||
|
"api-key": config.apiKey ?? "",
|
||||||
|
},
|
||||||
|
// Azure requires api-version as query parameter - store in provider config
|
||||||
|
azureResourceName: config.resourceName,
|
||||||
|
azureApiVersion: apiVersion,
|
||||||
|
models: [buildAzureOpenAIModelDefinition(config)],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a provider configuration for Azure OpenAI with multiple deployments
|
||||||
|
*
|
||||||
|
* Note: Each deployment gets its own baseUrl with the deployment name in the path.
|
||||||
|
* For multiple deployments, we use the first deployment in the baseUrl.
|
||||||
|
* The api-version query parameter is added automatically via a global fetch wrapper.
|
||||||
|
*/
|
||||||
|
export function buildAzureOpenAIProviderMultiDeployment(params: {
|
||||||
|
resourceName: string;
|
||||||
|
apiKey?: string;
|
||||||
|
apiVersion?: string;
|
||||||
|
deployments: Array<{
|
||||||
|
name: string;
|
||||||
|
displayName?: string;
|
||||||
|
reasoning?: boolean;
|
||||||
|
input?: Array<"text" | "image">;
|
||||||
|
contextWindow?: number;
|
||||||
|
maxTokens?: number;
|
||||||
|
}>;
|
||||||
|
}): ProviderConfig {
|
||||||
|
const apiVersion = params.apiVersion ?? AZURE_OPENAI_DEFAULT_API_VERSION;
|
||||||
|
const firstDeployment = params.deployments[0]?.name ?? "default";
|
||||||
|
const baseUrl = `https://${params.resourceName}.openai.azure.com/openai/deployments/${firstDeployment}`;
|
||||||
|
|
||||||
|
// Register the resource for fetch interception to add api-version query param
|
||||||
|
registerAzureOpenAIResource(params.resourceName, apiVersion);
|
||||||
|
// Install the global fetch wrapper
|
||||||
|
installAzureOpenAIFetchWrapper();
|
||||||
|
|
||||||
|
const models: ModelDefinitionConfig[] = params.deployments.map((deployment) => ({
|
||||||
|
id: deployment.name,
|
||||||
|
name: deployment.displayName ?? `Azure ${deployment.name}`,
|
||||||
|
reasoning: deployment.reasoning ?? false,
|
||||||
|
input: deployment.input ?? ["text"],
|
||||||
|
cost: AZURE_OPENAI_DEFAULT_COST,
|
||||||
|
contextWindow: deployment.contextWindow ?? AZURE_OPENAI_DEFAULT_CONTEXT_WINDOW,
|
||||||
|
maxTokens: deployment.maxTokens ?? AZURE_OPENAI_DEFAULT_MAX_TOKENS,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseUrl,
|
||||||
|
api: "openai-completions",
|
||||||
|
apiKey: params.apiKey,
|
||||||
|
authHeader: false,
|
||||||
|
headers: {
|
||||||
|
"api-key": params.apiKey ?? "",
|
||||||
|
},
|
||||||
|
azureResourceName: params.resourceName,
|
||||||
|
azureApiVersion: apiVersion,
|
||||||
|
models,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment variable names for Azure OpenAI configuration
|
||||||
|
*/
|
||||||
|
export const AZURE_OPENAI_ENV = {
|
||||||
|
API_KEY: "AZURE_OPENAI_API_KEY",
|
||||||
|
RESOURCE_NAME: "AZURE_OPENAI_RESOURCE_NAME",
|
||||||
|
DEPLOYMENT_NAME: "AZURE_OPENAI_DEPLOYMENT_NAME",
|
||||||
|
API_VERSION: "AZURE_OPENAI_API_VERSION",
|
||||||
|
ENDPOINT: "AZURE_OPENAI_ENDPOINT",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves Azure OpenAI configuration from environment variables
|
||||||
|
*/
|
||||||
|
export function resolveAzureOpenAIConfigFromEnv(
|
||||||
|
env: NodeJS.ProcessEnv = process.env,
|
||||||
|
): AzureOpenAIConfig | null {
|
||||||
|
const apiKey = env[AZURE_OPENAI_ENV.API_KEY]?.trim();
|
||||||
|
const resourceName = env[AZURE_OPENAI_ENV.RESOURCE_NAME]?.trim();
|
||||||
|
const deploymentName = env[AZURE_OPENAI_ENV.DEPLOYMENT_NAME]?.trim();
|
||||||
|
const apiVersion = env[AZURE_OPENAI_ENV.API_VERSION]?.trim();
|
||||||
|
|
||||||
|
// If endpoint is provided directly, parse resource name from it
|
||||||
|
const endpoint = env[AZURE_OPENAI_ENV.ENDPOINT]?.trim();
|
||||||
|
let resolvedResourceName = resourceName;
|
||||||
|
if (!resolvedResourceName && endpoint) {
|
||||||
|
const match = /https:\/\/([^.]+)\.openai\.azure\.com/.exec(endpoint);
|
||||||
|
if (match) {
|
||||||
|
resolvedResourceName = match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!apiKey || !resolvedResourceName || !deploymentName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
resourceName: resolvedResourceName,
|
||||||
|
deploymentName,
|
||||||
|
apiVersion: apiVersion || AZURE_OPENAI_DEFAULT_API_VERSION,
|
||||||
|
apiKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default export for common models available on Azure OpenAI
|
||||||
|
* These are the most commonly deployed models
|
||||||
|
*/
|
||||||
|
export const AZURE_OPENAI_COMMON_MODELS = {
|
||||||
|
"gpt-5": {
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text", "image"] as Array<"text" | "image">,
|
||||||
|
contextWindow: AZURE_OPENAI_DEFAULT_CONTEXT_WINDOW,
|
||||||
|
maxTokens: AZURE_OPENAI_DEFAULT_MAX_TOKENS,
|
||||||
|
},
|
||||||
|
"gpt-5-mini": {
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text", "image"] as Array<"text" | "image">,
|
||||||
|
contextWindow: AZURE_OPENAI_DEFAULT_CONTEXT_WINDOW,
|
||||||
|
maxTokens: AZURE_OPENAI_DEFAULT_MAX_TOKENS,
|
||||||
|
},
|
||||||
|
"gpt-5-nano": {
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text", "image"] as Array<"text" | "image">,
|
||||||
|
contextWindow: AZURE_OPENAI_DEFAULT_CONTEXT_WINDOW,
|
||||||
|
maxTokens: AZURE_OPENAI_DEFAULT_MAX_TOKENS,
|
||||||
|
},
|
||||||
|
"gpt-5-codex": {
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"] as Array<"text" | "image">,
|
||||||
|
contextWindow: AZURE_OPENAI_DEFAULT_CONTEXT_WINDOW,
|
||||||
|
maxTokens: AZURE_OPENAI_DEFAULT_MAX_TOKENS,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
@ -286,6 +286,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
|
|||||||
venice: "VENICE_API_KEY",
|
venice: "VENICE_API_KEY",
|
||||||
mistral: "MISTRAL_API_KEY",
|
mistral: "MISTRAL_API_KEY",
|
||||||
opencode: "OPENCODE_API_KEY",
|
opencode: "OPENCODE_API_KEY",
|
||||||
|
"azure-openai": "AZURE_OPENAI_API_KEY",
|
||||||
};
|
};
|
||||||
const envVar = envMap[normalized];
|
const envVar = envMap[normalized];
|
||||||
if (!envVar) return null;
|
if (!envVar) return null;
|
||||||
|
|||||||
@ -13,6 +13,11 @@ import {
|
|||||||
SYNTHETIC_MODEL_CATALOG,
|
SYNTHETIC_MODEL_CATALOG,
|
||||||
} from "./synthetic-models.js";
|
} from "./synthetic-models.js";
|
||||||
import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js";
|
import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js";
|
||||||
|
import {
|
||||||
|
buildAzureOpenAIProvider,
|
||||||
|
resolveAzureOpenAIConfigFromEnv,
|
||||||
|
AZURE_OPENAI_ENV,
|
||||||
|
} from "./azure-openai-provider.js";
|
||||||
|
|
||||||
type ModelsConfig = NonNullable<OpenClawConfig["models"]>;
|
type ModelsConfig = NonNullable<OpenClawConfig["models"]>;
|
||||||
export type ProviderConfig = NonNullable<ModelsConfig["providers"]>[string];
|
export type ProviderConfig = NonNullable<ModelsConfig["providers"]>[string];
|
||||||
@ -454,6 +459,30 @@ export async function resolveImplicitProviders(params: {
|
|||||||
providers.ollama = { ...(await buildOllamaProvider()), apiKey: ollamaKey };
|
providers.ollama = { ...(await buildOllamaProvider()), apiKey: ollamaKey };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Azure OpenAI provider - auto-discover from environment variables
|
||||||
|
const azureOpenAIConfig = resolveAzureOpenAIConfigFromEnv();
|
||||||
|
if (azureOpenAIConfig) {
|
||||||
|
providers["azure-openai"] = buildAzureOpenAIProvider(azureOpenAIConfig);
|
||||||
|
} else {
|
||||||
|
// Check for API key in auth profiles
|
||||||
|
const azureOpenAIKey =
|
||||||
|
resolveEnvApiKeyVarName("azure-openai") ??
|
||||||
|
resolveApiKeyFromProfiles({ provider: "azure-openai", store: authStore });
|
||||||
|
if (azureOpenAIKey) {
|
||||||
|
// If we have an API key but not full config, check for resource/deployment in env
|
||||||
|
const resourceName = process.env[AZURE_OPENAI_ENV.RESOURCE_NAME]?.trim();
|
||||||
|
const deploymentName = process.env[AZURE_OPENAI_ENV.DEPLOYMENT_NAME]?.trim();
|
||||||
|
if (resourceName && deploymentName) {
|
||||||
|
providers["azure-openai"] = buildAzureOpenAIProvider({
|
||||||
|
resourceName,
|
||||||
|
deploymentName,
|
||||||
|
apiKey: azureOpenAIKey,
|
||||||
|
apiVersion: process.env[AZURE_OPENAI_ENV.API_VERSION]?.trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return providers;
|
return providers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,6 +41,10 @@ export type ModelProviderConfig = {
|
|||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
authHeader?: boolean;
|
authHeader?: boolean;
|
||||||
models: ModelDefinitionConfig[];
|
models: ModelDefinitionConfig[];
|
||||||
|
/** Azure OpenAI specific: resource name */
|
||||||
|
azureResourceName?: string;
|
||||||
|
/** Azure OpenAI specific: API version */
|
||||||
|
azureApiVersion?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BedrockDiscoveryConfig = {
|
export type BedrockDiscoveryConfig = {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user