Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
27 changes: 27 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,30 @@ 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 credentials (or use AWS_PROFILE from ~/.aws/config and ~/.aws/credentials)
# AWS_REGION=eu-central-1
# AWS_PROFILE=default
# AWS_ACCESS_KEY_ID=your-access-key-id
# AWS_SECRET_ACCESS_KEY=your-secret-access-key
#
# Default models used when selecting aliases (sonnet/opus/haiku) in the UI
# ANTHROPIC_DEFAULT_SONNET_MODEL=eu.anthropic.claude-sonnet-4-5-20250929-v1:0
Comment thread
tintnc marked this conversation as resolved.
Outdated
# ANTHROPIC_DEFAULT_OPUS_MODEL=eu.anthropic.claude-opus-4-5-20251101-v1:0
# ANTHROPIC_DEFAULT_HAIKU_MODEL=eu.anthropic.claude-haiku-4-5-20251001-v1:0
#
# Optional explicit model overrides used by Claude SDK
# ANTHROPIC_MODEL=eu.anthropic.claude-sonnet-4-5-20250929-v1:0
# ANTHROPIC_SMALL_FAST_MODEL=eu.anthropic.claude-haiku-4-5-20251001-v1:0

2 changes: 0 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 72 additions & 5 deletions server/claude-sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,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, images } = options;

const sdkOptions = {};
Expand Down Expand Up @@ -190,9 +190,10 @@ 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_MODELS.DEFAULT;
// Map model (default to sonnet). In Bedrock mode this resolves model aliases
// (sonnet/opus/haiku) to configured inference profile IDs when available.
const settingsEnv = await loadClaudeSettingsEnv();
sdkOptions.model = resolveClaudeModel(options.model, settingsEnv);
console.log(`Using model: ${sdkOptions.model}`);

// Map system prompt configuration
Expand Down Expand Up @@ -459,6 +460,72 @@ async function loadMcpConfig(cwd) {
}
}

function isTruthyValue(value) {
if (typeof value !== 'string') {
return false;
}

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

async function loadClaudeSettingsEnv() {
try {
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
const content = await fs.readFile(settingsPath, 'utf8');
const parsed = JSON.parse(content);
if (parsed?.env && typeof parsed.env === 'object') {
return parsed.env;
}
} catch {
// Ignore missing/malformed settings and fall back to process.env.
}

return {};
}

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 '';
}

function resolveClaudeModel(modelAlias, settingsEnv) {
const requestedModel = modelAlias || CLAUDE_MODELS.DEFAULT;
const isBedrockEnabled = isTruthyValue(resolveClaudeEnvValue('CLAUDE_CODE_USE_BEDROCK', settingsEnv));
if (!isBedrockEnabled) {
return requestedModel;
}

const explicitModel = resolveClaudeEnvValue('ANTHROPIC_MODEL', settingsEnv);
Comment thread
tintnc marked this conversation as resolved.
const explicitFastModel = resolveClaudeEnvValue('ANTHROPIC_SMALL_FAST_MODEL', settingsEnv);
const sonnetDefault = resolveClaudeEnvValue('ANTHROPIC_DEFAULT_SONNET_MODEL', settingsEnv);
const opusDefault = resolveClaudeEnvValue('ANTHROPIC_DEFAULT_OPUS_MODEL', settingsEnv);
const haikuDefault = resolveClaudeEnvValue('ANTHROPIC_DEFAULT_HAIKU_MODEL', settingsEnv);

if (requestedModel === 'haiku') {
return haikuDefault || explicitFastModel || explicitModel || requestedModel;
}

if (requestedModel === 'opus' || requestedModel === 'opusplan') {
return opusDefault || explicitModel || requestedModel;
}

if (requestedModel === 'sonnet' || requestedModel === 'sonnet[1m]') {
return sonnetDefault || explicitModel || requestedModel;
}

return explicitModel || requestedModel;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Executes a Claude query using the SDK
* @param {string} command - User prompt/command
Expand All @@ -483,7 +550,7 @@ async function queryClaudeSDK(command, options = {}, ws) {

try {
// Map CLI options to SDK format
const sdkOptions = mapCliOptionsToSDK(options);
const sdkOptions = await mapCliOptionsToSDK(options);

// Load MCP configuration
const mcpServers = await loadMcpConfig(options.cwd);
Expand Down
45 changes: 37 additions & 8 deletions server/routes/cli-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import os from 'os';

const router = express.Router();

function isTruthyValue(value) {
if (typeof value !== 'string') {
return false;
}

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

router.get('/claude/status', async (req, res) => {
try {
const credentialsResult = await checkClaudeCredentials();
Expand All @@ -14,14 +23,16 @@ router.get('/claude/status', async (req, res) => {
return res.json({
authenticated: true,
email: credentialsResult.email || 'Authenticated',
method: credentialsResult.method // 'api_key' or 'credentials_file'
method: credentialsResult.method, // 'api_key', 'credentials_file', or 'bedrock'
isBedrock: Boolean(credentialsResult.isBedrock)
});
}

return res.json({
authenticated: false,
email: null,
method: null,
isBedrock: false,
error: credentialsResult.error || 'Not authenticated'
});

Expand All @@ -31,6 +42,7 @@ router.get('/claude/status', async (req, res) => {
authenticated: false,
email: null,
method: null,
isBedrock: false,
error: error.message
});
}
Expand Down Expand Up @@ -134,6 +146,20 @@ async function loadClaudeSettingsEnv() {
* - method: 'api_key' for env var, 'credentials_file' for OAuth tokens
*/
async function checkClaudeCredentials() {
const settingsEnv = await loadClaudeSettingsEnv();

// Priority 0: Bedrock mode bypasses Claude OAuth checks.
const bedrockEnabled = isTruthyValue(process.env.CLAUDE_CODE_USE_BEDROCK)
|| isTruthyValue(settingsEnv.CLAUDE_CODE_USE_BEDROCK);
if (bedrockEnabled) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bedrock enablement logic is duplicated and can diverge from SDK behavior.

This inline check can drift from the resolver used in server/claude-sdk.js, causing status/UI to report Bedrock while SDK model resolution behaves differently. Centralize Bedrock detection in one shared helper and reuse it in both files.

Proposed fix
-import { isTruthyValue, loadClaudeSettingsEnv } from '../utils/env-helpers.js';
+import { isBedrockEnabled, loadClaudeSettingsEnv } from '../utils/env-helpers.js';
@@
-  const bedrockEnabled = isTruthyValue(process.env.CLAUDE_CODE_USE_BEDROCK)
-    || isTruthyValue(settingsEnv.CLAUDE_CODE_USE_BEDROCK);
+  const bedrockEnabled = isBedrockEnabled(settingsEnv);
// server/utils/env-helpers.js
export function isBedrockEnabled(settingsEnv = {}) {
  return isTruthyValue(process.env.CLAUDE_CODE_USE_BEDROCK)
    || isTruthyValue(settingsEnv.CLAUDE_CODE_USE_BEDROCK);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/cli-auth.js` around lines 127 - 130, The bedrock enablement
check is duplicated (the local bedrockEnabled variable using isTruthyValue and
settingsEnv) and should be centralized: add a new helper function
isBedrockEnabled(settingsEnv = {}) in server/utils/env-helpers.js that returns
the existing expression (isTruthyValue(process.env.CLAUDE_CODE_USE_BEDROCK) ||
isTruthyValue(settingsEnv.CLAUDE_CODE_USE_BEDROCK)), then update the usage in
server/routes/cli-auth.js (replace the bedrockEnabled declaration) and in
server/claude-sdk.js (replace the resolver’s bedrock check) to import and call
isBedrockEnabled so both files rely on the same implementation.

return {
authenticated: true,
email: 'AWS Bedrock',
method: 'bedrock',
isBedrock: true
};
}

// Priority 1: Check for ANTHROPIC_API_KEY environment variable
// The SDK checks this first and uses it if present, even if OAuth tokens exist.
// When set, API calls are charged via pay-as-you-go rates instead of subscription.
Expand All @@ -148,21 +174,21 @@ async function checkClaudeCredentials() {
// Priority 1b: Check ~/.claude/settings.json env values.
// Claude Code can read proxy/auth values from settings.json even when the
// CloudCLI server process itself was not started with those env vars exported.
const settingsEnv = await loadClaudeSettingsEnv();

if (typeof settingsEnv.ANTHROPIC_API_KEY === 'string' && settingsEnv.ANTHROPIC_API_KEY.trim()) {
return {
authenticated: true,
email: 'API Key Auth',
method: 'api_key'
method: 'api_key',
isBedrock: false
};
}

if (typeof settingsEnv.ANTHROPIC_AUTH_TOKEN === 'string' && settingsEnv.ANTHROPIC_AUTH_TOKEN.trim()) {
return {
authenticated: true,
email: 'Configured via settings.json',
method: 'api_key'
method: 'api_key',
isBedrock: false
};
}

Expand All @@ -182,21 +208,24 @@ async function checkClaudeCredentials() {
return {
authenticated: true,
email: creds.email || creds.user || null,
method: 'credentials_file'
method: 'credentials_file',
isBedrock: false
};
}
}

return {
authenticated: false,
email: null,
method: null
method: null,
isBedrock: false
};
} catch (error) {
return {
authenticated: false,
email: null,
method: null
method: null,
isBedrock: false
};
}
}
Expand Down
1 change: 1 addition & 0 deletions src/components/settings/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const DEFAULT_AUTH_STATUS: AuthStatus = {
email: null,
loading: true,
error: null,
isBedrock: false,
};

export const DEFAULT_MCP_TEST_RESULT: McpTestResult = {
Expand Down
3 changes: 3 additions & 0 deletions src/components/settings/hooks/useSettingsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type StatusApiResponse = {
email?: string | null;
error?: string | null;
method?: string;
isBedrock?: boolean;
};

type JsonResult = {
Expand Down Expand Up @@ -289,6 +290,7 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
loading: false,
error: data.error || null,
method: data.method,
isBedrock: Boolean(data.isBedrock),
});
} catch (error) {
console.error(`Error checking ${provider} auth status:`, error);
Expand All @@ -297,6 +299,7 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
email: null,
loading: false,
error: getErrorMessage(error),
isBedrock: false,
});
}
}, [setAuthStatusByProvider]);
Expand Down
1 change: 1 addition & 0 deletions src/components/settings/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type AuthStatus = {
loading: boolean;
error: string | null;
method?: string;
isBedrock?: boolean;
};

export type KeyValueMap = Record<string, string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,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