feat: switch providers for loaded Claude sessions (#1002)

### Problem

Provider routing is captured when the Claude SDK `Query` is created. As
a result, `providers/set` and `providers/disable` updated newly created
sessions but an already loaded ACP session continued using its previous
backend.

User and project Claude settings could also override the ACP-selected
URL or credentials after the subprocess environment was assembled,
making the configured proxy ineffective.

### Solution

- Serialize provider changes and wait for submitted turns to settle.
- Close and recreate every loaded SDK query with the same Claude session
ID, cwd, MCP configuration, and ACP creation parameters.
- Block new/load/resume/prompt requests only while a provider update is
in progress.
- Apply the active ACP route in both the subprocess environment and
programmatic settings tier.
- Clear competing Anthropic, Bedrock, Vertex, OAuth, API-key, and
`apiKeyHelper` routing while an ACP override is active; preserve
unrelated settings.
- Restore native/environment routing when the provider is disabled.
- Add opt-in routing diagnostics through `CLAUDE_AGENT_LOGS` without
logging request content or secret headers.

### Tests

Added coverage for:

- native behavior when Providers API is unused;
- live recreation of loaded sessions;
- waiting for active turns;
- multiple loaded sessions across native → proxy A → proxy B → native;
- user settings precedence and preservation of unrelated settings;
- Anthropic, Bedrock, Vertex, legacy gateway auth, and logout behavior.

Verification:

- `npm test`: 789 passed, 20 skipped
- `npm run build`
- `npm run check`
- `git diff --check`

---------

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
Sergey Shkredov
2026-08-17 14:49:34 +02:00
committed by GitHub
parent 59a7e9367b
commit 50a95434e9
6 changed files with 330 additions and 30 deletions

View File

@@ -434,6 +434,8 @@ type Turn = {
steeredSettle?: PromptResponse;
resolve: (response: PromptResponse) => void;
reject: (error: unknown) => void;
/** Settles after the ACP prompt request completes, regardless of outcome. */
completion?: Promise<void>;
};
type Session = {
@@ -531,6 +533,8 @@ type Session = {
/** Serialized snapshot of session-defining params (cwd, mcpServers) used to
* detect when loadSession/resumeSession is called with changed values. */
sessionFingerprint: string;
/** Original ACP parameters used to recreate this query with a new provider. */
creationParams?: NewSessionRequest;
settingsManager: SettingsManager;
accumulatedUsage: AccumulatedUsage;
modes: SessionModeState;
@@ -1505,6 +1509,8 @@ export class ClaudeAcpAgent {
gatewayAuthRequest?: GatewayAuthRequest;
/** Set while ACP overrides the agent's native provider configuration. */
providerConfig?: ProviderConfig;
/** Serializes provider changes while every open query is recreated between turns. */
private providerUpdate: Promise<void> | null = null;
/** Grace period before a `session/cancel` forces a wedged prompt loop to
* return "cancelled". See {@link DEFAULT_FORCE_CANCEL_GRACE_MS}. Mutable so
* tests can shrink it. */
@@ -1686,6 +1692,7 @@ export class ClaudeAcpAgent {
}
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
if (this.providerUpdate) await this.providerUpdate;
const response = await this.createSession(params, {
// Revisit these meta values once we support resume
resume: (params._meta as NewSessionMeta | undefined)?.claudeCode?.options?.resume,
@@ -1698,6 +1705,7 @@ export class ClaudeAcpAgent {
}
async unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse> {
if (this.providerUpdate) await this.providerUpdate;
const response = await this.createSession(
{
cwd: params.cwd,
@@ -1718,6 +1726,7 @@ export class ClaudeAcpAgent {
}
async resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
if (this.providerUpdate) await this.providerUpdate;
const result = await this.getOrCreateSession(params);
// Needs to happen after we return the session
@@ -1728,6 +1737,7 @@ export class ClaudeAcpAgent {
}
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
if (this.providerUpdate) await this.providerUpdate;
const result = await this.getOrCreateSession(params);
await this.replaySessionHistory(params.sessionId);
@@ -1802,6 +1812,9 @@ export class ClaudeAcpAgent {
async unstable_listProviders(_params: ListProvidersRequest): Promise<ListProvidersResponse> {
const config = this.providerConfig ?? this.defaultProviderConfig();
this.logger.log(
`[providers/list] apiType=${config.apiType} baseUrl=${config.baseUrl} overridden=${this.providerConfig !== undefined}`,
);
const provider: ProviderInfo = {
providerId: PROVIDER_ID,
supported: SUPPORTED_PROTOCOLS,
@@ -1862,7 +1875,10 @@ export class ClaudeAcpAgent {
config.vertex = { projectId: vertex.projectId, region: vertex.region };
}
this.providerConfig = config;
this.logger.log(
`[providers/set] apiType=${config.apiType} baseUrl=${config.baseUrl} sessions=${Object.keys(this.sessions).length}`,
);
await this.enqueueProviderUpdate(config);
return {};
}
@@ -1872,7 +1888,8 @@ export class ClaudeAcpAgent {
*/
async unstable_disableProvider(params: DisableProviderRequest): Promise<DisableProviderResponse> {
if (params.providerId === PROVIDER_ID) {
this.providerConfig = undefined;
this.logger.log(`[providers/disable] sessions=${Object.keys(this.sessions).length}`);
await this.enqueueProviderUpdate(undefined);
}
// Unknown provider: idempotent success.
return {};
@@ -1941,6 +1958,7 @@ export class ClaudeAcpAgent {
}
async prompt(params: PromptRequest): Promise<PromptResponse> {
if (this.providerUpdate) await this.providerUpdate;
const session = this.sessions[params.sessionId];
if (!session) {
throw new Error("Session not found");
@@ -1990,9 +2008,19 @@ export class ClaudeAcpAgent {
resolve: () => {},
reject: () => {},
};
let completeTurn!: () => void;
turn.completion = new Promise<void>((resolve) => {
completeTurn = resolve;
});
const response = new Promise<PromptResponse>((resolve, reject) => {
turn.resolve = resolve;
turn.reject = reject;
turn.resolve = (result) => {
resolve(result);
completeTurn();
};
turn.reject = (error) => {
reject(error);
completeTurn();
};
});
session.turnQueue ??= [];
@@ -6308,13 +6336,40 @@ export class ClaudeAcpAgent {
// config concurrently, so re-resolving it after any of the awaits between
// here and the session registration could disagree with the env baked
// into the query.
const resolvedProvider = this.resolveProviderConfig();
const providerEnv = createEnvForProvider(resolvedProvider);
const configuredSettings =
userProvidedOptions?.settings ??
(modelConfig
? {
...(modelConfig.modelOverrides && { modelOverrides: modelConfig.modelOverrides }),
...(modelConfig.availableModels && { availableModels: modelConfig.availableModels }),
}
: undefined);
// Claude Code applies env from settings.json after the subprocess env. Put
// an active ACP route in the programmatic settings tier too so user/project
// settings cannot silently restore a different ANTHROPIC_BASE_URL.
let settings = configuredSettings;
if (resolvedProvider) {
const baseSettings =
typeof configuredSettings === "string"
? (JSON.parse(
await fs.readFile(path.resolve(params.cwd, configuredSettings), "utf8"),
) as Exclude<Options["settings"], string | undefined>)
: configuredSettings;
settings = {
...baseSettings,
apiKeyHelper: "",
env: { ...baseSettings?.env, ...providerEnv },
};
}
const env = {
...process.env,
...userProvidedOptions?.env,
// Client-managed LLM routing: `providers/set` config wins, else the
// legacy gateway auth request. Baked into the query at creation, so it
// only affects sessions started after the change (matching the RFD).
...createEnvForProvider(this.resolveProviderConfig()),
// legacy gateway auth request. Routing is baked into the query at
// creation; provider updates recreate loaded queries between turns.
...providerEnv,
// Opt-in to session state events like when the agent is idle
CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: "1",
};
@@ -6323,23 +6378,16 @@ export class ClaudeAcpAgent {
// SDK, so per-session `_meta` env routing and ambient process-env routing
// are distinguished exactly as the CLI will see them.
const providerCacheKey = providerCacheKeyFor(env);
this.logger.log(
`[session/query] sessionId=${sessionId} resume=${creationOpts?.resume ?? "none"} apiType=${resolvedProvider?.apiType ?? "native"} baseUrl=${resolvedProvider?.baseUrl ?? "native"}`,
);
const options: Options = {
systemPrompt,
settingSources: ["user", "project", "local"],
...(thinking !== undefined && { thinking }),
...userProvidedOptions,
// CLAUDE_MODEL_CONFIG env var is a fallback for model
// configuration (e.g. Bedrock model ID overrides). When the caller
// provides settings via _meta, we intentionally ignore the env var —
// the caller is assumed to have full control over model configuration.
...(!userProvidedOptions?.settings &&
modelConfig && {
settings: {
...(modelConfig.modelOverrides && { modelOverrides: modelConfig.modelOverrides }),
...(modelConfig.availableModels && { availableModels: modelConfig.availableModels }),
},
}),
...(settings && { settings }),
env,
// Override certain fields that must be controlled by ACP
cwd: params.cwd,
@@ -6682,6 +6730,7 @@ export class ClaudeAcpAgent {
cancelled: false,
cwd: params.cwd,
sessionFingerprint: computeSessionFingerprint(params),
creationParams: params,
settingsManager,
accumulatedUsage: {
inputTokens: 0,
@@ -6721,6 +6770,46 @@ export class ClaudeAcpAgent {
configOptions,
};
}
/**
* Provider routing is baked into the environment of each SDK Query. Wait for
* all submitted turns to settle, close every query, then resume each Claude
* session with the same ID so subsequent turns inherit the new environment.
*/
private async enqueueProviderUpdate(config: ProviderConfig | undefined): Promise<void> {
const previous = this.providerUpdate?.catch(() => undefined) ?? Promise.resolve();
const update = previous.then(async () => {
const sessions = Object.entries(this.sessions);
const activeTurns = sessions.flatMap(([, session]) =>
(session.turnQueue ?? []).flatMap((turn) => (turn.completion ? [turn.completion] : [])),
);
if (activeTurns.length > 0) {
this.logger.log(
`Waiting for ${activeTurns.length} active Claude turn(s) before provider update`,
);
await Promise.all(activeTurns);
}
this.providerConfig = config;
for (const [sessionId, session] of sessions) {
if (this.sessions[sessionId] !== session || !session.creationParams) {
continue;
}
this.logger.log(`Recreating Claude session ${sessionId} for provider update`);
this.closeQueryStream(session);
delete this.sessions[sessionId];
await this.createSession(session.creationParams, { resume: sessionId });
}
});
this.providerUpdate = update;
try {
await update;
} finally {
if (this.providerUpdate === update) {
this.providerUpdate = null;
}
}
}
}
function shouldEmitRawMessage(
@@ -6834,14 +6923,28 @@ function createEnvForProvider(config: ProviderConfig | null): Record<string, str
if (!config) {
return {};
}
const resetRouting = {
ANTHROPIC_BASE_URL: "",
ANTHROPIC_BEDROCK_BASE_URL: "",
ANTHROPIC_VERTEX_BASE_URL: "",
CLAUDE_CODE_USE_BEDROCK: "0",
CLAUDE_CODE_USE_VERTEX: "0",
ANTHROPIC_VERTEX_PROJECT_ID: "",
CLOUD_ML_REGION: "",
AWS_REGION: "",
ANTHROPIC_API_KEY: "",
ANTHROPIC_AUTH_TOKEN: "",
CLAUDE_CODE_OAUTH_TOKEN: "",
};
const customHeaders = Object.entries(config.headers)
.map(([key, value]) => `${key}: ${value}`)
.join("\n");
if (config.apiType === "bedrock") {
return {
...resetRouting,
CLAUDE_CODE_USE_BEDROCK: "1",
AWS_BEARER_TOKEN_BEDROCK: " ", // Must be non-empty to bypass pass configuration check
AWS_BEARER_TOKEN_BEDROCK: "acp-proxy", // Bypass local AWS credential checks
ANTHROPIC_BEDROCK_BASE_URL: config.baseUrl,
ANTHROPIC_CUSTOM_HEADERS: customHeaders,
};
@@ -6851,6 +6954,7 @@ function createEnvForProvider(config: ProviderConfig | null): Record<string, str
// `config.vertex` is guaranteed present for vertex by `unstable_setProvider`
// validation; fall back to empty strings defensively.
return {
...resetRouting,
CLAUDE_CODE_USE_VERTEX: "1",
ANTHROPIC_VERTEX_BASE_URL: config.baseUrl,
ANTHROPIC_VERTEX_PROJECT_ID: config.vertex?.projectId ?? "",
@@ -6860,9 +6964,10 @@ function createEnvForProvider(config: ProviderConfig | null): Record<string, str
}
return {
...resetRouting,
ANTHROPIC_BASE_URL: config.baseUrl,
ANTHROPIC_CUSTOM_HEADERS: customHeaders,
ANTHROPIC_AUTH_TOKEN: " ", // Must be specified to bypass claude login requirement
ANTHROPIC_AUTH_TOKEN: "acp-proxy", // Bypass local Claude login checks
};
}
@@ -8675,7 +8780,7 @@ export async function runPromptWithCancellation(
}
}
export function runAcp() {
export function runAcp(logger?: Logger) {
const input = nodeToWebWritable(process.stdout);
const output = nodeToWebReadable(process.stdin);
@@ -8721,7 +8826,7 @@ export function runAcp() {
)
.connect(stream);
agent = new ClaudeAcpAgent(new ClientConnection(connection.client));
agent = new ClaudeAcpAgent(new ClientConnection(connection.client), logger);
return { connection, agent };
}
@@ -8819,6 +8924,7 @@ const PROVIDER_ROUTING_ENV_VARS = [
"ANTHROPIC_CUSTOM_HEADERS",
"ANTHROPIC_API_KEY",
"ANTHROPIC_AUTH_TOKEN",
"CLAUDE_CODE_OAUTH_TOKEN",
] as const;
/** Stable identifier for the LLM backend a session's query is created against,

View File

@@ -3,6 +3,8 @@
import { resolveSettings } from "@anthropic-ai/claude-agent-sdk";
import { claudeCliPath, runAcp } from "./acp-agent.js";
import packageJson from "../package.json" with { type: "json" };
import { appendFileSync, mkdirSync } from "node:fs";
import { join } from "node:path";
// `--cli` is checked first so that `--version`/`-v` (and any other flags) are
// forwarded to the wrapped native CLI rather than swallowed by our own version
@@ -61,7 +63,29 @@ if (process.argv.includes("--cli")) {
console.error("Unhandled Rejection at:", promise, "reason:", reason);
});
const { connection, agent } = runAcp();
const logDirectory = process.env.CLAUDE_AGENT_LOGS;
const logger = logDirectory
? (() => {
mkdirSync(logDirectory, { recursive: true });
const logFile = join(logDirectory, "agent.log");
const writeLog = (...args: unknown[]) => {
const rendered = args
.map((arg) => (arg instanceof Error ? (arg.stack ?? arg.message) : String(arg)))
.join(" ");
appendFileSync(logFile, `${new Date().toISOString()} pid=${process.pid} ${rendered}\n`);
};
return {
log: writeLog,
error: (...args: unknown[]) => {
console.error(...args);
writeLog(...args);
},
};
})()
: undefined;
logger?.log("Claude ACP started");
const { connection, agent } = runAcp(logger);
async function shutdown() {
await agent.dispose().catch((err) => {

View File

@@ -125,7 +125,7 @@ describe("authorization", () => {
expect.objectContaining({
options: expect.objectContaining({
env: expect.objectContaining({
ANTHROPIC_AUTH_TOKEN: " ",
ANTHROPIC_AUTH_TOKEN: "acp-proxy",
ANTHROPIC_BASE_URL: "https://gateway.example",
ANTHROPIC_CUSTOM_HEADERS: "x-api-key: test",
userEnv: "userEnv",
@@ -162,7 +162,7 @@ describe("authorization", () => {
options: expect.objectContaining({
env: expect.objectContaining({
CLAUDE_CODE_USE_BEDROCK: "1",
AWS_BEARER_TOKEN_BEDROCK: " ",
AWS_BEARER_TOKEN_BEDROCK: "acp-proxy",
ANTHROPIC_BEDROCK_BASE_URL: "https://gateway.example",
ANTHROPIC_CUSTOM_HEADERS: "custom-header: test",
}),

View File

@@ -740,6 +740,7 @@ describe("createSession options merging", () => {
headers: {},
});
const plain = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
const plainProviderCacheKey = sessionFor(plain.sessionId).providerCacheKey;
await agent.unstable_setProvider({
providerId: "main",
@@ -749,9 +750,7 @@ describe("createSession options merging", () => {
});
const beta = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(sessionFor(beta.sessionId).providerCacheKey).not.toBe(
sessionFor(plain.sessionId).providerCacheKey,
);
expect(sessionFor(beta.sessionId).providerCacheKey).not.toBe(plainProviderCacheKey);
});
});

View File

@@ -28,6 +28,8 @@ export function makeMockQuery(overrides: Record<string, unknown> = {}) {
setPermissionMode: async () => {},
supportedCommands: async () => [],
getContextUsage: async () => DEFAULT_CONTEXT_USAGE,
close: () => {},
interrupt: async () => undefined,
[Symbol.asyncIterator]: async function* () {},
...overrides,
};

View File

@@ -11,6 +11,10 @@ const mockQuery = vi.hoisted(() =>
setModel: vi.fn(),
setPermissionMode: vi.fn(),
supportedCommands: vi.fn().mockResolvedValue([]),
close: vi.fn(),
interrupt: vi.fn().mockResolvedValue(undefined),
getContextUsage: vi.fn().mockResolvedValue({ totalTokens: 0, rawMaxTokens: 200000 }),
[Symbol.asyncIterator]: async function* () {},
})),
);
@@ -190,7 +194,7 @@ describe("providers", () => {
expect.objectContaining({
options: expect.objectContaining({
env: expect.objectContaining({
ANTHROPIC_AUTH_TOKEN: " ",
ANTHROPIC_AUTH_TOKEN: "acp-proxy",
ANTHROPIC_BASE_URL: "https://gateway.example",
ANTHROPIC_CUSTOM_HEADERS: "x-api-key: test",
}),
@@ -199,6 +203,171 @@ describe("providers", () => {
);
});
it("keeps native session creation unchanged when the providers API is unused", async () => {
const [agent, mockQuery] = await createAgentMock();
await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
expect(mockQuery).toHaveBeenCalledOnce();
expect(mockQuery.mock.calls[0][0].options.resume).toBeUndefined();
expect(mockQuery.mock.results[0].value.close).not.toHaveBeenCalled();
});
it("recreates loaded sessions with the new provider between turns", async () => {
const [agent, mockQuery] = await createAgentMock();
await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
const first = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
const originalQuery = mockQuery.mock.results[0].value;
await agent.unstable_setProvider({
providerId: "main",
apiType: "anthropic",
baseUrl: "https://gateway.example",
});
expect(originalQuery.close).toHaveBeenCalledOnce();
expect(mockQuery).toHaveBeenCalledTimes(2);
expect(mockQuery.mock.calls[1][0]).toEqual(
expect.objectContaining({
options: expect.objectContaining({
resume: first.sessionId,
env: expect.objectContaining({ ANTHROPIC_BASE_URL: "https://gateway.example" }),
settings: expect.objectContaining({
apiKeyHelper: "",
env: expect.objectContaining({
ANTHROPIC_BASE_URL: "https://gateway.example",
ANTHROPIC_AUTH_TOKEN: "acp-proxy",
CLAUDE_CODE_OAUTH_TOKEN: "",
}),
}),
}),
}),
);
await agent.unstable_disableProvider({ providerId: "main" });
expect(mockQuery).toHaveBeenCalledTimes(3);
expect(mockQuery.mock.calls[2][0]).toEqual(
expect.objectContaining({
options: expect.objectContaining({ resume: first.sessionId }),
}),
);
expect(mockQuery.mock.calls[2][0].options.env.ANTHROPIC_BASE_URL).toBe(
process.env.ANTHROPIC_BASE_URL,
);
});
it("switches every loaded session through proxy replacements and back to native routing", async () => {
const [agent, mockQuery] = await createAgentMock();
await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
const first = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
const second = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
await agent.unstable_setProvider({
providerId: "main",
apiType: "anthropic",
baseUrl: "https://first-gateway.example",
});
await agent.unstable_setProvider({
providerId: "main",
apiType: "anthropic",
baseUrl: "https://second-gateway.example",
});
await agent.unstable_disableProvider({ providerId: "main" });
expect(mockQuery).toHaveBeenCalledTimes(8);
const resumedCalls = mockQuery.mock.calls.slice(2);
for (let update = 0; update < 3; update += 1) {
expect(
resumedCalls.slice(update * 2, update * 2 + 2).map(([request]) => request.options.resume),
).toEqual([first.sessionId, second.sessionId]);
}
expect(
resumedCalls.slice(0, 2).map(([request]) => request.options.env.ANTHROPIC_BASE_URL),
).toEqual(["https://first-gateway.example", "https://first-gateway.example"]);
expect(
resumedCalls.slice(2, 4).map(([request]) => request.options.env.ANTHROPIC_BASE_URL),
).toEqual(["https://second-gateway.example", "https://second-gateway.example"]);
expect(
resumedCalls.slice(4).map(([request]) => request.options.env.ANTHROPIC_BASE_URL),
).toEqual([process.env.ANTHROPIC_BASE_URL, process.env.ANTHROPIC_BASE_URL]);
for (const result of mockQuery.mock.results.slice(0, 6)) {
expect(result.value.close).toHaveBeenCalledOnce();
}
for (const result of mockQuery.mock.results.slice(6)) {
expect(result.value.close).not.toHaveBeenCalled();
}
});
it("overrides user routing settings while preserving unrelated settings", async () => {
const [agent, mockQuery] = await createAgentMock();
await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
await agent.unstable_setProvider({
providerId: "main",
apiType: "anthropic",
baseUrl: "https://gateway.example",
});
await agent.newSession({
cwd: process.cwd(),
mcpServers: [],
_meta: {
claudeCode: {
options: {
settings: {
apiKeyHelper: "unsafe-helper",
env: {
ANTHROPIC_BASE_URL: "https://user-gateway.example",
ANTHROPIC_API_KEY: "user-secret",
UNRELATED_SETTING: "preserved",
},
model: "claude-sonnet-4-6",
},
},
},
},
});
expect(mockQuery.mock.calls[0][0].options.settings).toEqual(
expect.objectContaining({
apiKeyHelper: "",
model: "claude-sonnet-4-6",
env: expect.objectContaining({
ANTHROPIC_BASE_URL: "https://gateway.example",
ANTHROPIC_API_KEY: "",
ANTHROPIC_AUTH_TOKEN: "acp-proxy",
UNRELATED_SETTING: "preserved",
}),
}),
);
});
it("waits for an active turn before recreating its session", async () => {
const [agent, mockQuery] = await createAgentMock();
await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
const created = await agent.newSession({ cwd: process.cwd(), mcpServers: [] });
const originalQuery = mockQuery.mock.results[0].value;
let finishTurn!: () => void;
const completion = new Promise<void>((resolve) => {
finishTurn = resolve;
});
(agent.sessions[created.sessionId] as any).turnQueue = [{ completion }];
const update = agent.unstable_setProvider({
providerId: "main",
apiType: "anthropic",
baseUrl: "https://gateway.example",
});
await Promise.resolve();
await Promise.resolve();
expect(originalQuery.close).not.toHaveBeenCalled();
finishTurn();
await update;
expect(originalQuery.close).toHaveBeenCalledOnce();
});
it("routes bedrock provider config into session env", async () => {
const [agent, mockQuery] = await createAgentMock();
await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
@@ -216,7 +385,7 @@ describe("providers", () => {
options: expect.objectContaining({
env: expect.objectContaining({
CLAUDE_CODE_USE_BEDROCK: "1",
AWS_BEARER_TOKEN_BEDROCK: " ",
AWS_BEARER_TOKEN_BEDROCK: "acp-proxy",
ANTHROPIC_BEDROCK_BASE_URL: "https://gateway.example",
ANTHROPIC_CUSTOM_HEADERS: "custom-header: test",
}),