diff --git a/server/claude-agent-sdk-manager.ts b/server/claude-agent-sdk-manager.ts index 8cd7d17..f236f13 100644 --- a/server/claude-agent-sdk-manager.ts +++ b/server/claude-agent-sdk-manager.ts @@ -277,6 +277,7 @@ export class ClaudeAgentSDKManager extends BaseProcessManager { private buildSDKOptions(codon: Codon, previousSessionId: string | null): Options { // Model override is already applied in loadCodonSequence(), so just use codon.model const modelInfo = codon.model; + const isBedrock = modelInfo.providerId.toLowerCase() === "amazon-bedrock"; const options: Options = { model: modelInfo.modelId, @@ -286,6 +287,13 @@ export class ClaudeAgentSDKManager extends BaseProcessManager { settingSources: ["user"], }; + // Enable Bedrock mode if using Bedrock model + if (isBedrock) { + if (!options.env) options.env = {}; + options.env.CLAUDE_CODE_USE_BEDROCK = "1"; + this.logger.log("Enabling Bedrock mode for Claude SDK"); + } + // Use custom Claude Code executable path if provided if (process.env.CLAUDE_PATH_TO_CLAUDE_EXECUTABLE) { options.pathToClaudeCodeExecutable = process.env.CLAUDE_PATH_TO_CLAUDE_EXECUTABLE; @@ -370,6 +378,29 @@ export class ClaudeAgentSDKManager extends BaseProcessManager { this.logger.log(`Using custom Anthropic base URL: ${this.anthropicBaseUrl}`); } + // Pass through AWS credentials for Bedrock + if (isBedrock) { + const awsKeys = [ + "AWS_PROFILE", + "AWS_REGION", + "AWS_DEFAULT_REGION", + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY", + "AWS_SESSION_TOKEN", + "AWS_BEARER_TOKEN_BEDROCK", + "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", + "AWS_CONTAINER_CREDENTIALS_FULL_URI", + "AWS_WEB_IDENTITY_TOKEN_FILE", + ]; + + for (const key of awsKeys) { + if (process.env[key]) { + options.env[key] = process.env[key]; + this.logger.log(`Passing through AWS credential: ${key}`); + } + } + } + // Add codon-specific environment variables from config // These will override any existing variables with the same name if (codon.env) { @@ -708,9 +739,10 @@ export class ClaudeAgentSDKManager extends BaseProcessManager { * Run self-test to verify Claude Agent SDK environment setup. * Checks for API authentication (API key or OAuth token) and SDK availability. * + * @param modelInfo - Optional model info to check for Bedrock-specific requirements * @returns Promise resolving to self-test results */ - async runSelfTest(): Promise { + async runSelfTest(modelInfo?: import("./llm/models-dev-schema.js").ModelInfo): Promise { this.logger.log("Running Claude Agent SDK self-test..."); const checks: ShimSelfTestResult["checks"] = []; @@ -808,6 +840,54 @@ export class ClaudeAgentSDKManager extends BaseProcessManager { }); } + // Check 5: Bedrock authentication (if Bedrock model) + if (modelInfo?.providerId.toLowerCase() === "amazon-bedrock") { + const hasAwsProfile = !!process.env.AWS_PROFILE; + const hasAwsKeys = !!( + process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY + ); + const hasAwsBearerToken = !!process.env.AWS_BEARER_TOKEN_BEDROCK; + const hasAwsContainer = !!( + process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || + process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI + ); + const hasAwsWebIdentity = !!process.env.AWS_WEB_IDENTITY_TOKEN_FILE; + + const hasAwsAuth = + hasAwsProfile || hasAwsKeys || hasAwsBearerToken || hasAwsContainer || hasAwsWebIdentity; + + if (!hasAwsAuth) { + checks.push({ + name: "aws_credentials", + passed: false, + message: "No AWS credentials found (AWS_PROFILE, AWS_ACCESS_KEY_ID, etc.)", + }); + } else { + const authMethods = []; + if (hasAwsProfile) authMethods.push(`profile: ${process.env.AWS_PROFILE}`); + if (hasAwsKeys) authMethods.push("access keys"); + if (hasAwsBearerToken) authMethods.push("bearer token"); + if (hasAwsContainer) authMethods.push("container credentials"); + if (hasAwsWebIdentity) authMethods.push("web identity"); + + checks.push({ + name: "aws_credentials", + passed: true, + message: `AWS credentials detected (${authMethods.join(", ")})`, + }); + } + + // Check AWS region + const hasRegion = !!(process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION); + checks.push({ + name: "aws_region", + passed: hasRegion, + message: hasRegion + ? `Region: ${process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION}` + : "No AWS_REGION or AWS_DEFAULT_REGION set", + }); + } + // Overall result const allPassed = checks.every((check) => check.passed); diff --git a/server/codon-runner.ts b/server/codon-runner.ts index 083b7fe..20b0aa1 100644 --- a/server/codon-runner.ts +++ b/server/codon-runner.ts @@ -419,7 +419,9 @@ export class CodonRunner extends TypedEventEmitter { logger: Logger, anthropicBaseUrl?: string, ): Promise { - const isAnthropicModel = modelInfo.providerId.toLowerCase() === "anthropic"; + const isClaudeModel = ["anthropic", "amazon-bedrock"].includes( + modelInfo.providerId.toLowerCase(), + ); // Create temporary log parser (required by managers) const tempLogParserPath = path.join(os.tmpdir(), `self-test-parser-${Date.now()}.jsonl`); @@ -433,8 +435,8 @@ export class CodonRunner extends TypedEventEmitter { try { let result: ShimSelfTestResult; - if (isAnthropicModel) { - // Use Claude Agent SDK Manager for Anthropic models + if (isClaudeModel) { + // Use Claude Agent SDK Manager for Claude models (Anthropic or Bedrock) logger.log( `Testing Claude Agent SDK for model: ${modelInfo.name} (${modelInfo.providerId}/${modelInfo.modelId})`, "info", @@ -449,7 +451,7 @@ export class CodonRunner extends TypedEventEmitter { anthropicBaseUrl, ); - result = await manager.runSelfTest(); + result = await manager.runSelfTest(modelInfo); } else { // Use Shim Process Manager for non-Anthropic models logger.log( @@ -580,12 +582,14 @@ export class CodonRunner extends TypedEventEmitter { ); } else { const modelInfo = this.config.codon.model; - const isAnthropicModel = modelInfo.providerId.toLowerCase() === "anthropic"; + const isClaudeModel = ["anthropic", "amazon-bedrock"].includes( + modelInfo.providerId.toLowerCase(), + ); - if (isAnthropicModel) { - // Use Claude Agent SDK for Anthropic models + if (isClaudeModel) { + // Use Claude Agent SDK for Claude models (Anthropic or Bedrock) this.config.logger.log( - `Using Claude Agent SDK for Anthropic model: ${modelInfo.name} (${modelInfo.providerId}/${modelInfo.modelId})`, + `Using Claude Agent SDK for model: ${modelInfo.name} (${modelInfo.providerId}/${modelInfo.modelId})`, "info", ); @@ -599,7 +603,7 @@ export class CodonRunner extends TypedEventEmitter { this.config.shimIdleTimeout, ); } else { - // Use Shim for non-Anthropic models (e.g., Gemini) + // Use Shim for non-Claude models (e.g., Gemini, OpenAI) this.config.logger.log( `Using shim for model: ${modelInfo.name} (${modelInfo.providerId}/${modelInfo.modelId})`, "info",