feat: unify Claude API key and profile system with flexible key sourcing

- Add ApiKeySource type ('inline' | 'env' | 'credentials') to ClaudeApiProfile
- Allow profiles to source API keys from credentials.json or environment variables
- Add provider templates: OpenRouter, MiniMax, MiniMax (China)
- Auto-migrate existing users with Anthropic key to "Direct Anthropic" profile
- Update all API call sites to pass credentials for key resolution
- Add API key source selector to profile creation UI
- Increment settings version to 5 for migration support

This allows users to:
- Share a single API key across multiple profile configurations
- Use environment variables for CI/CD deployments
- Easily switch between providers without re-entering keys
This commit is contained in:
Stefan de Vogelaere
2026-01-19 17:28:28 +01:00
parent 10b49bd3b4
commit b88c940a36
25 changed files with 706 additions and 71 deletions

View File

@@ -162,6 +162,7 @@ export type {
EventHookAction,
EventHook,
// Claude API profile types
ApiKeySource,
ClaudeApiProfile,
ClaudeApiProfileTemplate,
} from './settings.js';

View File

@@ -2,7 +2,7 @@
* Shared types for AI model providers
*/
import type { ThinkingLevel, ClaudeApiProfile } from './settings.js';
import type { ThinkingLevel, ClaudeApiProfile, Credentials } from './settings.js';
import type { CodexSandboxMode, CodexApprovalPolicy } from './codex.js';
/**
@@ -215,6 +215,11 @@ export interface ExecuteOptions {
* When undefined, uses direct Anthropic API (via API key or Claude Max CLI OAuth).
*/
claudeApiProfile?: ClaudeApiProfile;
/**
* Credentials for resolving 'credentials' apiKeySource in Claude API profiles.
* When a profile has apiKeySource='credentials', the Anthropic key from this object is used.
*/
credentials?: Credentials;
}
/**

View File

@@ -105,6 +105,15 @@ export type ModelProvider = 'claude' | 'cursor' | 'codex' | 'opencode';
// Claude API Profiles - Configuration for Claude-compatible API endpoints
// ============================================================================
/**
* ApiKeySource - Strategy for sourcing API keys
*
* - 'inline': API key stored directly in the profile (legacy/default behavior)
* - 'env': Use ANTHROPIC_API_KEY environment variable
* - 'credentials': Use the Anthropic key from Settings → API Keys (credentials.json)
*/
export type ApiKeySource = 'inline' | 'env' | 'credentials';
/**
* ClaudeApiProfile - Configuration for a Claude-compatible API endpoint
*
@@ -117,8 +126,15 @@ export interface ClaudeApiProfile {
name: string;
/** ANTHROPIC_BASE_URL - custom API endpoint */
baseUrl: string;
/** API key value */
apiKey: string;
/**
* API key sourcing strategy (default: 'inline' for backwards compatibility)
* - 'inline': Use apiKey field value
* - 'env': Use ANTHROPIC_API_KEY environment variable
* - 'credentials': Use the Anthropic key from credentials.json
*/
apiKeySource?: ApiKeySource;
/** API key value (only required when apiKeySource = 'inline' or undefined) */
apiKey?: string;
/** If true, use ANTHROPIC_AUTH_TOKEN instead of ANTHROPIC_API_KEY */
useAuthToken?: boolean;
/** API_TIMEOUT_MS override in milliseconds */
@@ -140,6 +156,8 @@ export interface ClaudeApiProfile {
export interface ClaudeApiProfileTemplate {
name: string;
baseUrl: string;
/** Default API key source for this template (user chooses when creating) */
defaultApiKeySource?: ApiKeySource;
useAuthToken: boolean;
timeoutMs?: number;
modelMappings?: ClaudeApiProfile['modelMappings'];
@@ -150,9 +168,26 @@ export interface ClaudeApiProfileTemplate {
/** Predefined templates for known Claude-compatible providers */
export const CLAUDE_API_PROFILE_TEMPLATES: ClaudeApiProfileTemplate[] = [
{
name: 'Direct Anthropic',
baseUrl: 'https://api.anthropic.com',
defaultApiKeySource: 'credentials',
useAuthToken: false,
description: 'Standard Anthropic API with your API key',
apiKeyUrl: 'https://console.anthropic.com/settings/keys',
},
{
name: 'OpenRouter',
baseUrl: 'https://openrouter.ai/api',
defaultApiKeySource: 'inline',
useAuthToken: true,
description: 'Access Claude and 300+ models via OpenRouter',
apiKeyUrl: 'https://openrouter.ai/keys',
},
{
name: 'z.AI GLM',
baseUrl: 'https://api.z.ai/api/anthropic',
defaultApiKeySource: 'inline',
useAuthToken: true,
timeoutMs: 3000000,
modelMappings: {
@@ -164,6 +199,36 @@ export const CLAUDE_API_PROFILE_TEMPLATES: ClaudeApiProfileTemplate[] = [
description: '3× usage at fraction of cost via GLM Coding Plan',
apiKeyUrl: 'https://z.ai/manage-apikey/apikey-list',
},
{
name: 'MiniMax',
baseUrl: 'https://api.minimax.io/anthropic',
defaultApiKeySource: 'inline',
useAuthToken: true,
timeoutMs: 3000000,
modelMappings: {
haiku: 'MiniMax-M2.1',
sonnet: 'MiniMax-M2.1',
opus: 'MiniMax-M2.1',
},
disableNonessentialTraffic: true,
description: 'MiniMax M2.1 coding model with extended context',
apiKeyUrl: 'https://platform.minimax.io/user-center/basic-information/interface-key',
},
{
name: 'MiniMax (China)',
baseUrl: 'https://api.minimaxi.com/anthropic',
defaultApiKeySource: 'inline',
useAuthToken: true,
timeoutMs: 3000000,
modelMappings: {
haiku: 'MiniMax-M2.1',
sonnet: 'MiniMax-M2.1',
opus: 'MiniMax-M2.1',
},
disableNonessentialTraffic: true,
description: 'MiniMax M2.1 for users in China',
apiKeyUrl: 'https://platform.minimaxi.com/user-center/basic-information/interface-key',
},
// Future: Add AWS Bedrock, Google Vertex, etc.
];
@@ -906,7 +971,7 @@ export const DEFAULT_PHASE_MODELS: PhaseModelConfig = {
};
/** Current version of the global settings schema */
export const SETTINGS_VERSION = 4;
export const SETTINGS_VERSION = 5;
/** Current version of the credentials schema */
export const CREDENTIALS_VERSION = 1;
/** Current version of the project settings schema */