import { afterAll, beforeAll, describe, expect, it } from "bun:test";
import { Effort } from "../src/model-thinking";
import { streamBedrock } from "../src/providers/amazon-bedrock";
import type { Context, Model } from "../src/types";
const originalSkipAuth = process.env.AWS_BEDROCK_SKIP_AUTH;
beforeAll(() => {
process.env.AWS_BEDROCK_SKIP_AUTH = "1";
});
afterAll(() => {
if (originalSkipAuth === undefined) delete process.env.AWS_BEDROCK_SKIP_AUTH;
else process.env.AWS_BEDROCK_SKIP_AUTH = originalSkipAuth;
});
function adaptiveModel(id: string): Model<"bedrock-converse-stream"> {
return {
id,
name: id,
api: "bedrock-converse-stream",
provider: "amazon-bedrock",
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 1_000_000,
maxTokens: 128_000,
thinking: { mode: "anthropic-adaptive", minLevel: Effort.Minimal, maxLevel: Effort.XHigh },
};
}
function budgetModel(id: string): Model<"bedrock-converse-stream"> {
return {
id,
name: id,
api: "bedrock-converse-stream",
provider: "amazon-bedrock",
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200_000,
maxTokens: 64_000,
thinking: { mode: "budget", minLevel: Effort.Minimal, maxLevel: Effort.High },
};
}
const baseContext: Context = {
systemPrompt: ["You are concise."],
messages: [{ role: "user", content: "ping", timestamp: Date.now() }],
};
function abortedSignal(): AbortSignal {
const controller = new AbortController();
controller.abort();
return controller.signal;
}
interface ThinkingPayload {
additionalModelRequestFields?: {
thinking?: { type?: string; display?: string; budget_tokens?: number };
};
}
function captureBedrockPayload(
model: Model<"bedrock-converse-stream">,
options: Parameters<typeof streamBedrock>[2] = {},
): Promise<ThinkingPayload> {
const { promise, resolve } = Promise.withResolvers<ThinkingPayload>();
void streamBedrock(model, baseContext, {
signal: abortedSignal(),
...options,
onPayload: payload => {
resolve(payload as ThinkingPayload);
return undefined;
},
});
return promise;
}
describe("issue #1373: Bedrock Claude thinkingDisplay", () => {
it("defaults adaptive thinking to display=summarized on Opus 4.7+", async () => {
const payload = await captureBedrockPayload(adaptiveModel("anthropic.claude-opus-4-7"), {
reasoning: Effort.High,
});
expect(payload.additionalModelRequestFields?.thinking).toMatchObject({
type: "adaptive",
display: "summarized",
});
});
it("respects explicit thinkingDisplay='omitted' on Opus 4.7+", async () => {
const payload = await captureBedrockPayload(adaptiveModel("eu.anthropic.claude-opus-4-7"), {
reasoning: Effort.High,
thinkingDisplay: "omitted",
});
expect(payload.additionalModelRequestFields?.thinking).toMatchObject({
type: "adaptive",
display: "omitted",
});
});
it("omits display on adaptive Opus 4.6 (older models reject the field)", async () => {
const payload = await captureBedrockPayload(adaptiveModel("global.anthropic.claude-opus-4-6-v1"), {
reasoning: Effort.High,
});
const thinking = payload.additionalModelRequestFields?.thinking;
expect(thinking?.type).toBe("adaptive");
expect(thinking?.display).toBeUndefined();
});
it("sends display=summarized by default on budget-based thinking models", async () => {
const payload = await captureBedrockPayload(budgetModel("us.anthropic.claude-haiku-4-5-20251001-v1:0"), {
reasoning: Effort.High,
});
expect(payload.additionalModelRequestFields?.thinking).toMatchObject({
type: "enabled",
display: "summarized",
});
expect(typeof payload.additionalModelRequestFields?.thinking?.budget_tokens).toBe("number");
});
});