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
|
||||
# Must be a WhatsApp-enabled Twilio number, prefixed with whatsapp:
|
||||
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",
|
||||
mistral: "MISTRAL_API_KEY",
|
||||
opencode: "OPENCODE_API_KEY",
|
||||
"azure-openai": "AZURE_OPENAI_API_KEY",
|
||||
};
|
||||
const envVar = envMap[normalized];
|
||||
if (!envVar) return null;
|
||||
|
||||
@ -13,6 +13,11 @@ import {
|
||||
SYNTHETIC_MODEL_CATALOG,
|
||||
} from "./synthetic-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"]>;
|
||||
export type ProviderConfig = NonNullable<ModelsConfig["providers"]>[string];
|
||||
@ -454,6 +459,30 @@ export async function resolveImplicitProviders(params: {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -41,6 +41,10 @@ export type ModelProviderConfig = {
|
||||
headers?: Record<string, string>;
|
||||
authHeader?: boolean;
|
||||
models: ModelDefinitionConfig[];
|
||||
/** Azure OpenAI specific: resource name */
|
||||
azureResourceName?: string;
|
||||
/** Azure OpenAI specific: API version */
|
||||
azureApiVersion?: string;
|
||||
};
|
||||
|
||||
export type BedrockDiscoveryConfig = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user