diff --git a/packages/adapters/claude-local/src/server/execute.ts b/packages/adapters/claude-local/src/server/execute.ts index 04153410..e55717d8 100644 --- a/packages/adapters/claude-local/src/server/execute.ts +++ b/packages/adapters/claude-local/src/server/execute.ts @@ -32,6 +32,7 @@ import { isClaudeUnknownSessionError, } from "./parse.js"; import { resolveClaudeDesiredSkillNames } from "./skills.js"; +import { isBedrockModelId } from "./models.js"; const __moduleDir = path.dirname(fileURLToPath(import.meta.url)); @@ -439,9 +440,12 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0) args.push("--max-turns", String(maxTurns)); if (effectiveInstructionsFilePath) { diff --git a/packages/adapters/claude-local/src/server/index.ts b/packages/adapters/claude-local/src/server/index.ts index 86377011..5da522a6 100644 --- a/packages/adapters/claude-local/src/server/index.ts +++ b/packages/adapters/claude-local/src/server/index.ts @@ -1,5 +1,6 @@ export { execute, runClaudeLogin } from "./execute.js"; export { listClaudeSkills, syncClaudeSkills } from "./skills.js"; +export { listClaudeModels } from "./models.js"; export { testEnvironment } from "./test.js"; export { parseClaudeStreamJson, diff --git a/packages/adapters/claude-local/src/server/models.ts b/packages/adapters/claude-local/src/server/models.ts new file mode 100644 index 00000000..044fc3d2 --- /dev/null +++ b/packages/adapters/claude-local/src/server/models.ts @@ -0,0 +1,33 @@ +import type { AdapterModel } from "@paperclipai/adapter-utils"; +import { models as DIRECT_MODELS } from "../index.js"; + +/** AWS Bedrock model IDs — region-qualified identifiers required by the Bedrock API. */ +const BEDROCK_MODELS: AdapterModel[] = [ + { id: "us.anthropic.claude-opus-4-6-v1", label: "Bedrock Opus 4.6" }, + { id: "us.anthropic.claude-sonnet-4-5-20250929-v2:0", label: "Bedrock Sonnet 4.5" }, + { id: "us.anthropic.claude-haiku-4-5-20251001-v1:0", label: "Bedrock Haiku 4.5" }, +]; + +function isBedrockEnv(): boolean { + return ( + process.env.CLAUDE_CODE_USE_BEDROCK === "1" || + process.env.CLAUDE_CODE_USE_BEDROCK === "true" || + (typeof process.env.ANTHROPIC_BEDROCK_BASE_URL === "string" && + process.env.ANTHROPIC_BEDROCK_BASE_URL.trim().length > 0) + ); +} + +/** + * Return the model list appropriate for the current auth mode. + * When Bedrock env vars are detected, returns Bedrock-native model IDs; + * otherwise returns standard Anthropic API model IDs. + */ +export async function listClaudeModels(): Promise { + return isBedrockEnv() ? BEDROCK_MODELS : DIRECT_MODELS; +} + +/** Check whether a model ID is a Bedrock-native identifier (not an Anthropic API short name). */ +/** Bedrock model IDs use region-qualified prefixes (e.g. us.anthropic.*, eu.anthropic.*) or ARNs. */ +export function isBedrockModelId(model: string): boolean { + return /^\w+\.anthropic\./.test(model) || model.startsWith("arn:aws:bedrock:"); +} diff --git a/packages/adapters/claude-local/src/server/test.ts b/packages/adapters/claude-local/src/server/test.ts index 31b8f3c0..be29c0e4 100644 --- a/packages/adapters/claude-local/src/server/test.ts +++ b/packages/adapters/claude-local/src/server/test.ts @@ -16,6 +16,7 @@ import { } from "@paperclipai/adapter-utils/server-utils"; import path from "node:path"; import { detectClaudeLoginRequired, parseClaudeStreamJson } from "./parse.js"; +import { isBedrockModelId } from "./models.js"; function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] { if (checks.some((check) => check.level === "error")) return "fail"; @@ -163,10 +164,10 @@ export async function testEnvironment( const args = ["--print", "-", "--output-format", "stream-json", "--verbose"]; if (dangerouslySkipPermissions) args.push("--dangerously-skip-permissions"); if (chrome) args.push("--chrome"); - // Skip --model for Bedrock: Anthropic-style model IDs (e.g. "claude-opus-4-6") are not - // valid Bedrock model identifiers. Let the CLI use whatever model is configured in its - // own settings when Bedrock auth is active. - if (model && !hasBedrock) args.push("--model", model); + // For Bedrock: only pass --model when the ID is a Bedrock-native identifier. + if (model && (!hasBedrock || isBedrockModelId(model))) { + args.push("--model", model); + } if (effort) args.push("--effort", effort); if (maxTurns > 0) args.push("--max-turns", String(maxTurns)); if (extraArgs.length > 0) args.push(...extraArgs); diff --git a/server/src/adapters/registry.ts b/server/src/adapters/registry.ts index 8a30a9c8..f6746227 100644 --- a/server/src/adapters/registry.ts +++ b/server/src/adapters/registry.ts @@ -4,6 +4,7 @@ import { execute as claudeExecute, listClaudeSkills, syncClaudeSkills, + listClaudeModels, testEnvironment as claudeTestEnvironment, sessionCodec as claudeSessionCodec, getQuotaWindows as claudeGetQuotaWindows, @@ -94,6 +95,7 @@ const claudeLocalAdapter: ServerAdapterModule = { sessionCodec: claudeSessionCodec, sessionManagement: getAdapterSessionManagement("claude_local") ?? undefined, models: claudeModels, + listModels: listClaudeModels, supportsLocalAgentJwt: true, agentConfigurationDoc: claudeAgentConfigurationDoc, getQuotaWindows: claudeGetQuotaWindows,