Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ RUN mkdir -p /root/.clawdbot \
&& mkdir -p /root/clawd/skills

# Copy startup script
# Build cache bust: 2026-01-28-v26-browser-skill
# Build cache bust: 2026-02-04-v27-ai-gateway-compat
COPY start-moltbot.sh /usr/local/bin/start-moltbot.sh
RUN chmod +x /usr/local/bin/start-moltbot.sh

Expand Down
11 changes: 11 additions & 0 deletions src/gateway/env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ describe('buildEnvVars', () => {
expect(result.ANTHROPIC_API_KEY).toBeUndefined();
});

it('maps AI_GATEWAY_API_KEY to OPENAI_API_KEY for compat gateway', () => {
const env = createMockEnv({
AI_GATEWAY_API_KEY: 'sk-gateway-key',
AI_GATEWAY_BASE_URL: 'https://gateway.ai.cloudflare.com/v1/123/my-gw/compat',
});
const result = buildEnvVars(env);
expect(result.OPENAI_API_KEY).toBe('sk-gateway-key');
expect(result.OPENAI_BASE_URL).toBe('https://gateway.ai.cloudflare.com/v1/123/my-gw/compat');
expect(result.ANTHROPIC_API_KEY).toBeUndefined();
});

it('passes AI_GATEWAY_BASE_URL directly', () => {
const env = createMockEnv({
AI_GATEWAY_BASE_URL: 'https://gateway.ai.cloudflare.com/v1/123/my-gw/anthropic',
Expand Down
4 changes: 3 additions & 1 deletion src/gateway/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export function buildEnvVars(env: MoltbotEnv): Record<string, string> {

// Normalize the base URL by removing trailing slashes
const normalizedBaseUrl = env.AI_GATEWAY_BASE_URL?.replace(/\/+$/, '');
const isOpenAIGateway = normalizedBaseUrl?.endsWith('/openai');
const isOpenAIGateway =
normalizedBaseUrl?.endsWith('/openai') ||
normalizedBaseUrl?.endsWith('/compat');

// AI Gateway vars take precedence
// Map to the appropriate provider env var based on the gateway endpoint
Expand Down
25 changes: 19 additions & 6 deletions start-moltbot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -228,28 +228,41 @@ if (process.env.SLACK_BOT_TOKEN && process.env.SLACK_APP_TOKEN) {
// https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/openai
const baseUrl = (process.env.AI_GATEWAY_BASE_URL || process.env.ANTHROPIC_BASE_URL || '').replace(/\/+$/, '');
const isOpenAI = baseUrl.endsWith('/openai');
const isCompat = baseUrl.endsWith('/compat');

if (isOpenAI) {
if (isOpenAI || isCompat) {
// Create custom openai provider config with baseUrl override
// Omit apiKey so moltbot falls back to OPENAI_API_KEY env var
console.log('Configuring OpenAI provider with base URL:', baseUrl);
config.models = config.models || {};
config.models.providers = config.models.providers || {};
const compatModels = [
{ id: 'openai/gpt-5-mini', name: 'GPT-5 Mini', contextWindow: 200000 },
{ id: 'google-ai-studio/gemini-2.5-flash', name: 'Gemini 2.5 Flash', contextWindow: 128000 },
{ id: 'anthropic/claude-sonnet-4-5', name: 'Claude Sonnet 4.5', contextWindow: 200000 },
];
config.models.providers.openai = {
baseUrl: baseUrl,
api: 'openai-responses',
models: [
models: isCompat ? compatModels : [
{ id: 'gpt-5.2', name: 'GPT-5.2', contextWindow: 200000 },
{ id: 'gpt-5', name: 'GPT-5', contextWindow: 200000 },
{ id: 'gpt-4.5-preview', name: 'GPT-4.5 Preview', contextWindow: 128000 },
]
};
// Add models to the allowlist so they appear in /models
config.agents.defaults.models = config.agents.defaults.models || {};
config.agents.defaults.models['openai/gpt-5.2'] = { alias: 'GPT-5.2' };
config.agents.defaults.models['openai/gpt-5'] = { alias: 'GPT-5' };
config.agents.defaults.models['openai/gpt-4.5-preview'] = { alias: 'GPT-4.5' };
config.agents.defaults.model.primary = 'openai/gpt-5.2';
if (isCompat) {
config.agents.defaults.models['openai/gpt-5-mini'] = { alias: 'GPT-5 Mini' };
config.agents.defaults.models['google-ai-studio/gemini-2.5-flash'] = { alias: 'Gemini 2.5 Flash' };
config.agents.defaults.models['anthropic/claude-sonnet-4-5'] = { alias: 'Claude Sonnet 4.5' };
config.agents.defaults.model.primary = 'anthropic/claude-sonnet-4-5';
} else {
config.agents.defaults.models['openai/gpt-5.2'] = { alias: 'GPT-5.2' };
config.agents.defaults.models['openai/gpt-5'] = { alias: 'GPT-5' };
config.agents.defaults.models['openai/gpt-4.5-preview'] = { alias: 'GPT-4.5' };
config.agents.defaults.model.primary = 'openai/gpt-5.2';
}
} else if (baseUrl) {
console.log('Configuring Anthropic provider with base URL:', baseUrl);
config.models = config.models || {};
Expand Down