feat(cli): detect missing provider API keys and persist to .env

Adds a canonical PROVIDER_API_KEY_ENV mapping (14 providers including
the three dual-region pairs) and an ensure_api_key() helper. When the
selected provider's key is absent from the environment, the CLI prompts
via questionary.password, writes the value to .env via python-dotenv's
set_key (preserves existing lines), and exports it into os.environ so
the run continues without restart. Wired into cli/main.py right after
the region prompts so qwen-cn, glm-cn, and minimax-cn each check their
own region-specific key. openai_client refactored to consult the same
mapping, eliminating its private duplicate of provider→env-var data.
This commit is contained in:
Yijia-Xiao
2026-05-11 06:12:34 +00:00
parent d13e9b7946
commit 9f7abfcbd5
4 changed files with 261 additions and 24 deletions
+20 -23
View File
@@ -4,6 +4,7 @@ from typing import Any, Optional
from langchain_core.messages import AIMessage
from langchain_openai import ChatOpenAI
from .api_key_env import get_api_key_env
from .base_client import BaseLLMClient, normalize_content
from .capabilities import get_capabilities
from .validators import validate_model
@@ -135,26 +136,22 @@ _PASSTHROUGH_KWARGS = (
"api_key", "callbacks", "http_client", "http_async_client",
)
# Provider base URLs and API key env vars
_PROVIDER_CONFIG = {
"xai": ("https://api.x.ai/v1", "XAI_API_KEY"),
"deepseek": ("https://api.deepseek.com", "DEEPSEEK_API_KEY"),
# DashScope exposes two regional endpoints with separate accounts; an
# international key won't authenticate against the China endpoint and
# vice versa (fixes issue #758).
"qwen": ("https://dashscope-intl.aliyuncs.com/compatible-mode/v1", "DASHSCOPE_API_KEY"),
"qwen-cn": ("https://dashscope.aliyuncs.com/compatible-mode/v1", "DASHSCOPE_CN_API_KEY"),
# Zhipu exposes the same GLM models under two brands with separate
# accounts: Z.AI (international, api.z.ai) and BigModel
# (open.bigmodel.cn, China). Keys aren't interchangeable across them.
"glm": ("https://api.z.ai/api/paas/v4/", "ZHIPU_API_KEY"),
"glm-cn": ("https://open.bigmodel.cn/api/paas/v4/", "ZHIPU_CN_API_KEY"),
# MiniMax exposes two regional endpoints with separate keys; mainland
# Chinese users hit .com while global users hit .io.
"minimax": ("https://api.minimax.io/v1", "MINIMAX_API_KEY"),
"minimax-cn": ("https://api.minimaxi.com/v1", "MINIMAX_CN_API_KEY"),
"openrouter": ("https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"),
"ollama": ("http://localhost:11434/v1", None),
# Provider base URLs. API-key env vars live in api_key_env.PROVIDER_API_KEY_ENV
# (one canonical mapping consulted by both this client and the CLI's
# interactive key-prompt). Dual-region providers (qwen/glm/minimax) keep
# separate endpoints because international and China accounts cannot share
# credentials (#758).
_PROVIDER_BASE_URL = {
"xai": "https://api.x.ai/v1",
"deepseek": "https://api.deepseek.com",
"qwen": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
"qwen-cn": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"glm": "https://api.z.ai/api/paas/v4/",
"glm-cn": "https://open.bigmodel.cn/api/paas/v4/",
"minimax": "https://api.minimax.io/v1",
"minimax-cn": "https://api.minimaxi.com/v1",
"openrouter": "https://openrouter.ai/api/v1",
"ollama": "http://localhost:11434/v1",
}
@@ -185,9 +182,9 @@ class OpenAIClient(BaseLLMClient):
# Provider-specific base URL and auth. An explicit base_url on the
# client (e.g. a corporate proxy) takes precedence over the
# provider default so users can route through their own gateway.
if self.provider in _PROVIDER_CONFIG:
default_base, api_key_env = _PROVIDER_CONFIG[self.provider]
llm_kwargs["base_url"] = self.base_url or default_base
if self.provider in _PROVIDER_BASE_URL:
llm_kwargs["base_url"] = self.base_url or _PROVIDER_BASE_URL[self.provider]
api_key_env = get_api_key_env(self.provider)
if api_key_env:
api_key = os.environ.get(api_key_env)
if api_key: