Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,28 @@ VITE_CONTEXT_WINDOW=160000
CONTEXT_WINDOW=160000


# =============================================================================
# AWS BEDROCK CONFIGURATION FOR CLAUDE
# =============================================================================
#
# Prerequisites:
# - AWS CLI must be installed and configured
# - Run 'aws configure' to set up credentials and default region
# - Or provide AWS credentials through environment variables or IAM roles
#
# To use AWS Bedrock instead of the Anthropic API, uncomment the lines below.
# CLAUDE_CODE_USE_BEDROCK=1
#
# AWS authentication for Bedrock:
# - Prefer IAM role or AWS_PROFILE from ~/.aws/config and ~/.aws/credentials
# - Optionally use AWS_BEARER_TOKEN_BEDROCK for Bedrock API key auth
# AWS_REGION=eu-central-1
# AWS_PROFILE=default
# AWS_BEARER_TOKEN_BEDROCK=your-bedrock-bearer-token
#
# Model defaults for Bedrock are defined in shared/modelConstants.js.
# The UI aliases (sonnet/opus/haiku) automatically resolve to the correct
# Bedrock model IDs (anthropic.claude-*). Override only if you need a
# custom inference profile or a specific regional endpoint:
# ANTHROPIC_MODEL=us.anthropic.claude-sonnet-4-6

60 changes: 53 additions & 7 deletions server/claude-sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import crypto from 'crypto';
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import { CLAUDE_FALLBACK_MODELS } from './modules/providers/list/claude/claude-models.provider.js';
import {
CLAUDE_BEDROCK_MODELS,
CLAUDE_FALLBACK_MODELS,
} from './modules/providers/list/claude/claude-models.provider.js';
import { isTruthyValue, loadClaudeSettingsEnv } from './utils/env-helpers.js';
import { providerModelsService } from './modules/providers/services/provider-models.service.js';
import { resolveClaudeCodeExecutablePath } from './shared/claude-cli-path.js';
import {
Expand Down Expand Up @@ -146,7 +150,7 @@ function matchesToolPermission(entry, toolName, input) {
* @param {Object} options - CLI options
* @returns {Object} SDK-compatible options
*/
function mapCliOptionsToSDK(options = {}) {
async function mapCliOptionsToSDK(options = {}) {
const { sessionId, cwd, toolsSettings, permissionMode } = options;

const sdkOptions = {};
Expand Down Expand Up @@ -203,10 +207,9 @@ function mapCliOptionsToSDK(options = {}) {

sdkOptions.disallowedTools = settings.disallowedTools || [];

// Map model (default to sonnet)
// Valid models: sonnet, opus, haiku, opusplan, sonnet[1m]
sdkOptions.model = options.model || CLAUDE_FALLBACK_MODELS.DEFAULT;
// Model logged at query start below
// Map model. In Bedrock mode this resolves UI aliases to model IDs.
const settingsEnv = await loadClaudeSettingsEnv();
sdkOptions.model = resolveClaudeModel(options.model, settingsEnv);

// Map system prompt configuration
sdkOptions.systemPrompt = {
Expand Down Expand Up @@ -494,6 +497,49 @@ async function loadMcpConfig(cwd) {
}
}

function resolveClaudeEnvValue(key, settingsEnv) {
const processValue = process.env[key];
if (typeof processValue === 'string' && processValue.trim()) {
return processValue.trim();
}

const settingsValue = settingsEnv[key];
if (typeof settingsValue === 'string' && settingsValue.trim()) {
return settingsValue.trim();
}

return '';
}

/**
* Resolves a UI model alias (e.g. "sonnet") to the actual model ID.
*
* When Bedrock is enabled, looks up the alias in CLAUDE_BEDROCK_MODELS
* for sensible defaults. Users can still override via ANTHROPIC_MODEL
* (in env or ~/.claude/settings.json) for custom inference profiles.
*/
function resolveClaudeModel(modelAlias, settingsEnv) {
const requestedModel = modelAlias || CLAUDE_FALLBACK_MODELS.DEFAULT;
const isBedrockEnabled = isTruthyValue(resolveClaudeEnvValue('CLAUDE_CODE_USE_BEDROCK', settingsEnv));
if (!isBedrockEnabled) {
return requestedModel;
}

// If the caller passed a specific model ID (not a UI alias), honour it directly
const UI_ALIASES = new Set(Object.keys(CLAUDE_BEDROCK_MODELS));
if (modelAlias && !UI_ALIASES.has(requestedModel)) {
return requestedModel;
}

// Allow explicit env override for custom inference profiles / regions
const explicitModel = resolveClaudeEnvValue('ANTHROPIC_MODEL', settingsEnv);
if (explicitModel) {
return explicitModel;
}

return CLAUDE_BEDROCK_MODELS[requestedModel] || requestedModel;
}

/**
* Executes a Claude query using the SDK
* @param {string} command - User prompt/command
Expand Down Expand Up @@ -524,7 +570,7 @@ async function queryClaudeSDK(command, options = {}, ws) {
);

// Map CLI options to SDK format
const sdkOptions = mapCliOptionsToSDK({
const sdkOptions = await mapCliOptionsToSDK({
...options,
model: resolvedModel || options.model,
});
Expand Down
22 changes: 22 additions & 0 deletions server/modules/providers/list/claude/claude-auth.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,19 @@ type ClaudeCredentialsStatus = {
authenticated: boolean;
email: string | null;
method: string | null;
isBedrock?: boolean;
error?: string;
};

const isTruthyValue = (value: unknown): boolean => {
if (typeof value !== 'string') {
return false;
}

const normalized = value.trim().toLowerCase();
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
};

const hasErrorCode = (error: unknown, code: string): boolean => (
error instanceof Error && 'code' in error && error.code === code
);
Expand Down Expand Up @@ -59,6 +69,7 @@ export class ClaudeProviderAuth implements IProviderAuth {
authenticated: credentials.authenticated,
email: credentials.authenticated ? credentials.email || 'Authenticated' : credentials.email,
method: credentials.method,
isBedrock: Boolean(credentials.isBedrock),
error: credentials.authenticated ? undefined : credentials.error || 'Not authenticated',
};
}
Expand Down Expand Up @@ -88,6 +99,17 @@ export class ClaudeProviderAuth implements IProviderAuth {
}

const settingsEnv = await this.loadSettingsEnv();
const bedrockEnabled = isTruthyValue(process.env.CLAUDE_CODE_USE_BEDROCK)
|| isTruthyValue(readOptionalString(settingsEnv.CLAUDE_CODE_USE_BEDROCK));
if (bedrockEnabled) {
return {
authenticated: true,
email: 'AWS Bedrock',
method: 'bedrock',
isBedrock: true,
};
}

if (readOptionalString(settingsEnv.ANTHROPIC_API_KEY)) {
return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
}
Expand Down
10 changes: 10 additions & 0 deletions server/modules/providers/list/claude/claude-models.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ export const CLAUDE_FALLBACK_MODELS: ProviderModelsDefinition = {
],
DEFAULT: 'default',
};

export const CLAUDE_BEDROCK_MODELS: Record<string, string> = {
default: 'anthropic.claude-sonnet-4-6',
sonnet: 'anthropic.claude-sonnet-4-6',
'sonnet[1m]': 'anthropic.claude-sonnet-4-6',
opus: 'anthropic.claude-opus-4-6-v1',
opusplan: 'anthropic.claude-opus-4-6-v1',
haiku: 'anthropic.claude-haiku-4-5-20251001-v1:0',
};

type ClaudeInitEvent = {
sessionId?: string;
session_id?: string;
Expand Down
1 change: 1 addition & 0 deletions server/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ export type ProviderAuthStatus = {
authenticated: boolean;
email: string | null;
method: string | null;
isBedrock?: boolean;
error?: string;
};

Expand Down
39 changes: 39 additions & 0 deletions server/utils/env-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';

/**
* Returns true if the value is a string considered "truthy" for env flags
* (e.g. CLAUDE_CODE_USE_BEDROCK). Accepts '1', 'true', 'yes', 'on' (case-insensitive).
* @param {unknown} value
* @returns {boolean}
*/
export function isTruthyValue(value) {
if (typeof value !== 'string') {
return false;
}

const normalized = value.trim().toLowerCase();
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
}

/**
* Loads env key/value pairs from ~/.claude/settings.json (settings.env).
* Used for auth and model config that may be set in Claude Code settings.
* @returns {Promise<Record<string, unknown>>} Env object or {} on missing/malformed file.
*/
export async function loadClaudeSettingsEnv() {
try {
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
const content = await fs.readFile(settingsPath, 'utf8');
const settings = JSON.parse(content);

if (settings?.env && typeof settings.env === 'object') {
return settings.env;
}
} catch {
// Ignore missing or malformed settings and fall back to empty.
}

return {};
}
2 changes: 2 additions & 0 deletions src/components/provider-auth/hooks/useProviderAuthStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type ProviderAuthStatusPayload = {
authenticated?: boolean;
email?: string | null;
method?: string | null;
isBedrock?: boolean;
error?: string | null;
};

Expand All @@ -37,6 +38,7 @@ const toProviderAuthStatus = (
authenticated: Boolean(payload.authenticated),
email: payload.email ?? null,
method: payload.method ?? null,
isBedrock: Boolean(payload.isBedrock),
error: payload.error ?? fallbackError,
loading: false,
});
Expand Down
1 change: 1 addition & 0 deletions src/components/provider-auth/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type ProviderAuthStatus = {
authenticated: boolean;
email: string | null;
method: string | null;
isBedrock?: boolean;
error: string | null;
loading: boolean;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export default function AccountContent({ agent, authStatus, onLogin }: AccountCo
</div>
</div>

{authStatus.method !== 'api_key' && (
{authStatus.method !== 'api_key' && !authStatus.isBedrock && (
<div className="border-t border-border/50 pt-4">
<div className="flex items-center justify-between">
<div>
Expand Down