diff --git a/Dockerfile b/Dockerfile index d7fd5d31..1f77ee73 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/src/gateway/env.test.ts b/src/gateway/env.test.ts index 29f033db..bc948451 100644 --- a/src/gateway/env.test.ts +++ b/src/gateway/env.test.ts @@ -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', diff --git a/src/gateway/env.ts b/src/gateway/env.ts index a57e781b..c8edaad4 100644 --- a/src/gateway/env.ts +++ b/src/gateway/env.ts @@ -11,7 +11,9 @@ export function buildEnvVars(env: MoltbotEnv): Record { // 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 diff --git a/start-moltbot.sh b/start-moltbot.sh index 286a4d67..7f2d3bf0 100644 --- a/start-moltbot.sh +++ b/start-moltbot.sh @@ -228,17 +228,23 @@ 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 }, @@ -246,10 +252,17 @@ if (isOpenAI) { }; // 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 || {};