Files
automaker/docs/UNIFIED_API_KEY_PROFILES.md
Stefan de Vogelaere a1f234c7e2 feat: Claude Compatible Providers System (#629)
* feat: refactor Claude API Profiles to Claude Compatible Providers

- Rename ClaudeApiProfile to ClaudeCompatibleProvider with models[] array
- Each ProviderModel has mapsToClaudeModel field for Claude tier mapping
- Add providerType field for provider-specific icons (glm, minimax, openrouter)
- Add thinking level support for provider models in phase selectors
- Show all mapped Claude models per provider model (e.g., "Maps to Haiku, Sonnet, Opus")
- Add Bulk Replace feature to switch all phases to a provider at once
- Hide Bulk Replace button when no providers are enabled
- Fix project-level phaseModelOverrides not persisting after refresh
- Fix deleting last provider not persisting (remove empty array guard)
- Add getProviderByModelId() helper for all SDK routes
- Update all routes to pass provider config for provider models
- Update terminology from "profiles" to "providers" throughout UI
- Update documentation to reflect new provider system

* fix: atomic writer race condition and bulk replace reset to defaults

1. AtomicWriter Race Condition Fix (libs/utils/src/atomic-writer.ts):
   - Changed temp file naming from Date.now() to Date.now() + random hex
   - Uses crypto.randomBytes(4).toString('hex') for uniqueness
   - Prevents ENOENT errors when multiple concurrent writes happen
     within the same millisecond

2. Bulk Replace "Anthropic Direct" Reset (both dialogs):
   - When selecting "Anthropic Direct", now uses DEFAULT_PHASE_MODELS
   - Properly resets thinking levels and other settings to defaults
   - Added thinkingLevel to the change detection comparison
   - Affects both global and project-level bulk replace dialogs

* fix: update tests for new model resolver passthrough behavior

1. model-resolver tests:
   - Unknown models now pass through unchanged (provider model support)
   - Removed expectations for warnings on unknown models
   - Updated case sensitivity and edge case tests accordingly
   - Added tests for provider-like model names (GLM-4.7, MiniMax-M2.1)

2. atomic-writer tests:
   - Updated regex to match new temp file format with random suffix
   - Format changed from .tmp.{timestamp} to .tmp.{timestamp}.{hex}

* refactor: simplify getPhaseModelWithOverrides calls per code review

Address code review feedback on PR #629:
- Make settingsService parameter optional in getPhaseModelWithOverrides
- Function now handles undefined settingsService gracefully by returning defaults
- Remove redundant ternary checks in 4 call sites:
  - apps/server/src/routes/context/routes/describe-file.ts
  - apps/server/src/routes/context/routes/describe-image.ts
  - apps/server/src/routes/worktree/routes/generate-commit-message.ts
  - apps/server/src/services/auto-mode-service.ts
- Remove unused DEFAULT_PHASE_MODELS imports where applicable

* test: fix server tests for provider model passthrough behavior

- Update model-resolver.test.ts to expect unknown models to pass through
  unchanged (supports ClaudeCompatibleProvider models like GLM-4.7)
- Remove warning expectations for unknown models (valid for providers)
- Add missing getCredentials and getGlobalSettings mocks to
  ideation-service.test.ts for settingsService

* fix: address code review feedback for model providers

- Honor thinkingLevel in generate-commit-message.ts
- Pass claudeCompatibleProvider in ideation-service.ts for provider models
- Resolve provider configuration for model overrides in generate-suggestions.ts
- Update "Active Profile" to "Active Provider" label in project-claude-section
- Use substring instead of deprecated substr in api-profiles-section
- Preserve provider enabled state when editing in api-profiles-section

* fix: address CodeRabbit review issues for Claude Compatible Providers

- Fix TypeScript TS2339 error in generate-suggestions.ts where
  settingsService was narrowed to 'never' type in else branch
- Use DEFAULT_PHASE_MODELS per-phase defaults instead of hardcoded
  'sonnet' in settings-helpers.ts
- Remove duplicate eventHooks key in use-settings-migration.ts
- Add claudeCompatibleProviders to localStorage migration parsing
  and merging functions
- Handle canonical claude-* model IDs (claude-haiku, claude-sonnet,
  claude-opus) in project-models-section display names

This resolves the CI build failures and addresses code review feedback.

* fix: skip broken list-view-priority E2E test and add Priority column label

- Skip list-view-priority.spec.ts with TODO explaining the infrastructure
  issue: setupRealProject only sets localStorage but server settings
  take precedence with localStorageMigrated: true
- Add 'Priority' label to list-header.tsx for the priority column
  (was empty string, now shows proper header text)
- Increase column width to accommodate the label

The E2E test issue is that tests create features in a temp directory,
but the server loads from the E2E Test Project fixture path set in
setup-e2e-fixtures.mjs. Needs infrastructure fix to properly switch
projects or create features through UI instead of on disk.
2026-01-20 20:57:23 +01:00

324 lines
10 KiB
Markdown

# Claude Compatible Providers System
This document describes the implementation of Claude Compatible Providers, allowing users to configure alternative API endpoints that expose Claude-compatible models to the application.
## Overview
Claude Compatible Providers allow Automaker to work with third-party API endpoints that implement Claude's API protocol. This enables:
- **Cost savings**: Use providers like z.AI GLM or MiniMax at lower costs
- **Alternative models**: Access models like GLM-4.7 or MiniMax M2.1 through familiar interfaces
- **Flexibility**: Configure per-phase model selection to optimize for speed vs quality
- **Project overrides**: Use different providers for different projects
## Architecture
### Type Definitions
#### ClaudeCompatibleProvider
```typescript
export interface ClaudeCompatibleProvider {
id: string; // Unique identifier (UUID)
name: string; // Display name (e.g., "z.AI GLM")
baseUrl: string; // API endpoint URL
providerType?: string; // Provider type for icon/grouping (e.g., 'glm', 'minimax', 'openrouter')
apiKeySource?: ApiKeySource; // 'inline' | 'env' | 'credentials'
apiKey?: string; // API key (when apiKeySource = 'inline')
useAuthToken?: boolean; // Use ANTHROPIC_AUTH_TOKEN header
timeoutMs?: number; // Request timeout in milliseconds
disableNonessentialTraffic?: boolean; // Minimize non-essential API calls
enabled?: boolean; // Whether provider is active (default: true)
models?: ProviderModel[]; // Models exposed by this provider
}
```
#### ProviderModel
```typescript
export interface ProviderModel {
id: string; // Model ID sent to API (e.g., "GLM-4.7")
displayName: string; // Display name in UI (e.g., "GLM 4.7")
mapsToClaudeModel?: ClaudeModelAlias; // Which Claude tier this replaces ('haiku' | 'sonnet' | 'opus')
capabilities?: {
supportsVision?: boolean; // Whether model supports image inputs
supportsThinking?: boolean; // Whether model supports extended thinking
maxThinkingLevel?: ThinkingLevel; // Maximum thinking level if supported
};
}
```
#### PhaseModelEntry
Phase model configuration now supports provider models:
```typescript
export interface PhaseModelEntry {
providerId?: string; // Provider ID (undefined = native Claude)
model: string; // Model ID or alias
thinkingLevel?: ThinkingLevel; // 'none' | 'low' | 'medium' | 'high'
}
```
### Provider Templates
Available provider templates in `CLAUDE_PROVIDER_TEMPLATES`:
| Template | Provider Type | Base URL | Description |
| ---------------- | ------------- | ------------------------------------ | ----------------------------- |
| Direct Anthropic | anthropic | `https://api.anthropic.com` | Standard Anthropic API |
| OpenRouter | openrouter | `https://openrouter.ai/api` | Access Claude and 300+ models |
| z.AI GLM | glm | `https://api.z.ai/api/anthropic` | GLM models at lower cost |
| MiniMax | minimax | `https://api.minimax.io/anthropic` | MiniMax M2.1 model |
| MiniMax (China) | minimax | `https://api.minimaxi.com/anthropic` | MiniMax for China region |
### Model Mappings
Each provider model specifies which Claude model tier it maps to via `mapsToClaudeModel`:
**z.AI GLM:**
- `GLM-4.5-Air` → haiku
- `GLM-4.7` → sonnet, opus
**MiniMax:**
- `MiniMax-M2.1` → haiku, sonnet, opus
**OpenRouter:**
- `anthropic/claude-3.5-haiku` → haiku
- `anthropic/claude-3.5-sonnet` → sonnet
- `anthropic/claude-3-opus` → opus
## Server-Side Implementation
### API Key Resolution
The `buildEnv()` function in `claude-provider.ts` resolves API keys based on `apiKeySource`:
```typescript
function buildEnv(
providerConfig?: ClaudeCompatibleProvider,
credentials?: Credentials
): Record<string, string | undefined> {
if (providerConfig) {
let apiKey: string | undefined;
const source = providerConfig.apiKeySource ?? 'inline';
switch (source) {
case 'inline':
apiKey = providerConfig.apiKey;
break;
case 'env':
apiKey = process.env.ANTHROPIC_API_KEY;
break;
case 'credentials':
apiKey = credentials?.apiKeys?.anthropic;
break;
}
// ... build environment with resolved key
}
}
```
### Provider Lookup
The `getProviderByModelId()` helper resolves provider configuration from model IDs:
```typescript
export async function getProviderByModelId(
modelId: string,
settingsService: SettingsService,
logPrefix?: string
): Promise<{
provider?: ClaudeCompatibleProvider;
resolvedModel?: string;
credentials?: Credentials;
}>;
```
This is used by all routes that call the Claude SDK to:
1. Check if the model ID belongs to a provider
2. Get the provider configuration (baseUrl, auth, etc.)
3. Resolve the `mapsToClaudeModel` for the SDK
### Phase Model Resolution
The `getPhaseModelWithOverrides()` helper gets effective phase model config:
```typescript
export async function getPhaseModelWithOverrides(
phaseKey: PhaseModelKey,
settingsService: SettingsService,
projectPath?: string,
logPrefix?: string
): Promise<{
model: string;
thinkingLevel?: ThinkingLevel;
providerId?: string;
providerConfig?: ClaudeCompatibleProvider;
credentials?: Credentials;
}>;
```
This handles:
1. Project-level overrides (if projectPath provided)
2. Global phase model settings
3. Default fallback models
## UI Implementation
### Model Selection Dropdowns
Phase model selectors (`PhaseModelSelector`) display:
1. **Claude Models** - Native Claude models (Haiku, Sonnet, Opus)
2. **Provider Sections** - Each enabled provider as a separate group:
- Section header: `{provider.name} (via Claude)`
- Models with their mapped Claude tiers: "Maps to Haiku, Sonnet, Opus"
- Thinking level submenu for models that support it
### Provider Icons
Icons are determined by `providerType`:
- `glm` → Z logo
- `minimax` → MiniMax logo
- `openrouter` → OpenRouter logo
- Generic → OpenRouter as fallback
### Bulk Replace
The "Bulk Replace" feature allows switching all phase models to a provider at once:
1. Select a provider from the dropdown
2. Preview shows which models will be assigned:
- haiku phases → provider's haiku-mapped model
- sonnet phases → provider's sonnet-mapped model
- opus phases → provider's opus-mapped model
3. Apply replaces all phase model configurations
The Bulk Replace button only appears when at least one provider is enabled.
## Project-Level Overrides
Projects can override global phase model settings via `phaseModelOverrides`:
```typescript
interface Project {
// ...
phaseModelOverrides?: PhaseModelConfig; // Per-phase overrides
}
```
### Storage
Project overrides are stored in `.automaker/settings.json`:
```json
{
"phaseModelOverrides": {
"enhancementModel": {
"providerId": "provider-uuid",
"model": "GLM-4.5-Air",
"thinkingLevel": "none"
}
}
}
```
### Resolution Priority
1. Project override for specific phase (if set)
2. Global phase model setting
3. Default model for phase
## Migration
### v5 → v6 Migration
The system migrated from `claudeApiProfiles` to `claudeCompatibleProviders`:
```typescript
// Old: modelMappings object
{
modelMappings: {
haiku: 'GLM-4.5-Air',
sonnet: 'GLM-4.7',
opus: 'GLM-4.7'
}
}
// New: models array with mapsToClaudeModel
{
models: [
{ id: 'GLM-4.5-Air', displayName: 'GLM 4.5 Air', mapsToClaudeModel: 'haiku' },
{ id: 'GLM-4.7', displayName: 'GLM 4.7', mapsToClaudeModel: 'sonnet' },
{ id: 'GLM-4.7', displayName: 'GLM 4.7', mapsToClaudeModel: 'opus' },
]
}
```
The migration is automatic and preserves existing provider configurations.
## Files Changed
### Types
| File | Changes |
| ---------------------------- | -------------------------------------------------------------------- |
| `libs/types/src/settings.ts` | `ClaudeCompatibleProvider`, `ProviderModel`, `PhaseModelEntry` types |
| `libs/types/src/provider.ts` | `ExecuteOptions.claudeCompatibleProvider` field |
| `libs/types/src/index.ts` | Exports for new types |
### Server
| File | Changes |
| ---------------------------------------------- | -------------------------------------------------------- |
| `apps/server/src/providers/claude-provider.ts` | Provider config handling, buildEnv updates |
| `apps/server/src/lib/settings-helpers.ts` | `getProviderByModelId()`, `getPhaseModelWithOverrides()` |
| `apps/server/src/services/settings-service.ts` | v5→v6 migration |
| `apps/server/src/routes/**/*.ts` | Provider lookup for all SDK calls |
### UI
| File | Changes |
| -------------------------------------------------- | ----------------------------------------- |
| `apps/ui/src/.../phase-model-selector.tsx` | Provider model rendering, thinking levels |
| `apps/ui/src/.../bulk-replace-dialog.tsx` | Bulk replace feature |
| `apps/ui/src/.../api-profiles-section.tsx` | Provider management UI |
| `apps/ui/src/components/ui/provider-icon.tsx` | Provider-specific icons |
| `apps/ui/src/hooks/use-project-settings-loader.ts` | Load phaseModelOverrides |
## Testing
```bash
# Build and run
npm run build:packages
npm run dev:web
# Run server tests
npm run test:server
```
### Test Cases
1. **Provider setup**: Add z.AI GLM provider with inline API key
2. **Model selection**: Select GLM-4.7 for a phase, verify it appears in dropdown
3. **Thinking levels**: Select thinking level for provider model
4. **Bulk replace**: Switch all phases to a provider at once
5. **Project override**: Set per-project model override, verify it persists
6. **Provider deletion**: Delete all providers, verify empty state persists
## Future Enhancements
Potential improvements:
1. **Provider validation**: Test API connection before saving
2. **Usage tracking**: Show which phases use which provider
3. **Cost estimation**: Display estimated costs per provider
4. **Model capabilities**: Auto-detect supported features from provider