Compare commits

..

3 Commits

Author SHA1 Message Date
Zamil Majdy
1f8dfdbd07 refactor: read autopilot key from env directly in ChatConfig instead of Settings() 2026-03-27 00:13:44 +07:00
Zamil Majdy
6ca34743f0 fix: update docstrings to reference autopilot_api_key instead of api_key 2026-03-27 00:07:37 +07:00
Zamil Majdy
73399b3945 feat(backend): support dedicated OpenRouter API key for autopilot
Add AUTOPILOT_OPEN_ROUTER_API_KEY env var that, when set, is used
instead of OPEN_ROUTER_API_KEY for CoPilot/AutoPilot SDK calls.
Falls back to the regular key when not configured.
2026-03-27 00:03:32 +07:00
4 changed files with 36 additions and 10 deletions

View File

@@ -3,7 +3,7 @@
import os
from typing import Literal
from pydantic import Field, field_validator
from pydantic import Field, field_validator, model_validator
from pydantic_settings import BaseSettings
from backend.util.clients import OPENROUTER_BASE_URL
@@ -21,6 +21,11 @@ class ChatConfig(BaseSettings):
description="Model to use for generating session titles (should be fast/cheap)",
)
api_key: str | None = Field(default=None, description="OpenAI API key")
autopilot_api_key: str | None = Field(
default=None,
description="Dedicated OpenRouter API key for AutoPilot. "
"Falls back to api_key when not set.",
)
base_url: str | None = Field(
default=OPENROUTER_BASE_URL,
description="Base URL for API (e.g., for OpenRouter)",
@@ -118,7 +123,7 @@ class ChatConfig(BaseSettings):
use_openrouter: bool = Field(
default=True,
description="Enable routing API calls through the OpenRouter proxy. "
"The actual decision also requires ``api_key`` and ``base_url`` — "
"The actual decision also requires ``autopilot_api_key`` and ``base_url`` — "
"use the ``openrouter_active`` property for the final answer.",
)
use_claude_code_subscription: bool = Field(
@@ -163,7 +168,7 @@ class ChatConfig(BaseSettings):
"""True when OpenRouter is enabled AND credentials are usable.
Single source of truth for "will the SDK route through OpenRouter?".
Checks the flag *and* that ``api_key`` + a valid ``base_url`` are
Checks the flag *and* that ``autopilot_api_key`` + a valid ``base_url`` are
present — mirrors the fallback logic in ``_build_sdk_env``.
"""
if not self.use_openrouter:
@@ -171,7 +176,22 @@ class ChatConfig(BaseSettings):
base = (self.base_url or "").rstrip("/")
if base.endswith("/v1"):
base = base[:-3]
return bool(self.api_key and base and base.startswith("http"))
return bool(self.autopilot_api_key and base and base.startswith("http"))
@field_validator("autopilot_api_key", mode="before")
@classmethod
def get_autopilot_api_key(cls, v):
"""Get dedicated autopilot OpenRouter key from environment if not provided."""
if not v:
v = os.getenv("AUTOPILOT_OPEN_ROUTER_API_KEY")
return v
@model_validator(mode="after")
def _fallback_autopilot_api_key(self):
"""Fall back autopilot_api_key to api_key when not explicitly set."""
if not self.autopilot_api_key:
self.autopilot_api_key = self.api_key
return self
@property
def e2b_active(self) -> bool:

View File

@@ -524,9 +524,10 @@ def _build_sdk_env(
base = (config.base_url or "").rstrip("/")
if base.endswith("/v1"):
base = base[:-3]
env: dict[str, str] = {
"ANTHROPIC_BASE_URL": base,
"ANTHROPIC_AUTH_TOKEN": config.api_key or "",
"ANTHROPIC_AUTH_TOKEN": config.autopilot_api_key or "",
"ANTHROPIC_API_KEY": "", # force CLI to use AUTH_TOKEN
}
@@ -1776,10 +1777,10 @@ async def stream_chat_completion_sdk(
# Fail fast when no API credentials are available at all.
sdk_env = _build_sdk_env(session_id=session_id, user_id=user_id)
if not config.api_key and not config.use_claude_code_subscription:
if not config.autopilot_api_key and not config.use_claude_code_subscription:
raise RuntimeError(
"No API key configured. Set OPEN_ROUTER_API_KEY, "
"CHAT_API_KEY, or ANTHROPIC_API_KEY for API access, "
"No API key configured. Set AUTOPILOT_OPEN_ROUTER_API_KEY, "
"OPEN_ROUTER_API_KEY, CHAT_API_KEY, or ANTHROPIC_API_KEY for API access, "
"or CHAT_USE_CLAUDE_CODE_SUBSCRIPTION=true to use "
"Claude Code CLI subscription (requires `claude login`)."
)

View File

@@ -619,6 +619,11 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
anthropic_api_key: str = Field(default="", description="Anthropic API key")
groq_api_key: str = Field(default="", description="Groq API key")
open_router_api_key: str = Field(default="", description="Open Router API Key")
autopilot_open_router_api_key: str = Field(
default="",
description="Dedicated Open Router API Key for AutoPilot. "
"When set, autopilot uses this key instead of the shared open_router_api_key.",
)
llama_api_key: str = Field(default="", description="Llama API Key")
v0_api_key: str = Field(default="", description="v0 by Vercel API key")
webshare_proxy_username: str = Field(

View File

@@ -1,5 +1,5 @@
# Base stage for both dev and prod
FROM node:22.22-alpine3.23 AS base
FROM node:21-alpine AS base
WORKDIR /app
RUN corepack enable
COPY autogpt_platform/frontend/package.json autogpt_platform/frontend/pnpm-lock.yaml ./
@@ -33,7 +33,7 @@ ENV NEXT_PUBLIC_SOURCEMAPS="false"
RUN if [ "$NEXT_PUBLIC_PW_TEST" = "true" ]; then NEXT_PUBLIC_PW_TEST=true NODE_OPTIONS="--max-old-space-size=8192" pnpm build; else NODE_OPTIONS="--max-old-space-size=8192" pnpm build; fi
# Prod stage - based on NextJS reference Dockerfile https://github.com/vercel/next.js/blob/64271354533ed16da51be5dce85f0dbd15f17517/examples/with-docker/Dockerfile
FROM node:22.22-alpine3.23 AS prod
FROM node:21-alpine AS prod
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
WORKDIR /app