import { describe, expect, it, vi } from "vitest"; const bedrockMock = vi.hoisted(() => ({ constructorCalls: [] as Array>, })); vi.mock("@aws-sdk/client-bedrock-runtime", () => { class BedrockRuntimeServiceException extends Error {} class BedrockRuntimeClient { constructor(config: Record) { bedrockMock.constructorCalls.push(config); } send(): Promise { return Promise.reject(new Error("mock send")); } } class ConverseStreamCommand { readonly input: unknown; constructor(input: unknown) { this.input = input; } } return { BedrockRuntimeClient, BedrockRuntimeServiceException, ConverseStreamCommand, StopReason: { END_TURN: "end_turn", STOP_SEQUENCE: "stop_sequence", MAX_TOKENS: "max_tokens", MODEL_CONTEXT_WINDOW_EXCEEDED: "model_context_window_exceeded", TOOL_USE: "tool_use", }, CachePointType: { DEFAULT: "default" }, CacheTTL: { ONE_HOUR: "ONE_HOUR" }, ConversationRole: { ASSISTANT: "assistant", USER: "user" }, ImageFormat: { JPEG: "jpeg", PNG: "png", GIF: "gif", WEBP: "webp" }, ToolResultStatus: { ERROR: "error", SUCCESS: "success" }, }; }); import { getModel } from "../src/models.ts"; import { streamBedrock } from "../src/providers/amazon-bedrock.ts"; import type { Context, Message } from "../src/types.ts"; const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-sonnet-4-5-20250929-v1:0"); async function capturePayload(context: Context): Promise { let capturedPayload: unknown; const s = streamBedrock(baseModel, context, { cacheRetention: "none", signal: AbortSignal.abort(), onPayload: (payload) => { capturedPayload = payload; return payload; }, }); for await (const event of s) { if (event.type === "error") break; } return capturedPayload; } describe("bedrock convertMessages skips unknown content types", () => { it("skips unknown user content blocks instead of throwing", async () => { const messages: Message[] = [ { role: "user", content: [ { type: "text", text: "hello" }, { type: "unknown", data: "foo" }, ] as any, timestamp: Date.now(), }, ]; const payload = await capturePayload({ messages }); expect(payload).toBeDefined(); const p = payload as { messages: Array<{ role: string; content: unknown[] }> }; expect(p.messages).toHaveLength(1); expect(p.messages[0].content).toHaveLength(1); expect(p.messages[0].content[0]).toEqual({ text: "hello" }); }); it("skips unknown assistant content blocks instead of throwing", async () => { const messages: Message[] = [ { role: "assistant", content: [ { type: "text", text: "hello" }, { type: "unknown", data: "foo" }, ] as any, api: "bedrock-converse-stream", provider: "amazon-bedrock", model: baseModel.id, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }, stopReason: "stop", timestamp: Date.now(), }, ]; const payload = await capturePayload({ messages }); expect(payload).toBeDefined(); const p = payload as { messages: Array<{ role: string; content: unknown[] }> }; expect(p.messages).toHaveLength(1); expect(p.messages[0].content).toHaveLength(1); expect(p.messages[0].content[0]).toEqual({ text: "hello" }); }); it("replaces user messages with only unknown content blocks with a placeholder", async () => { const messages: Message[] = [ { role: "user", content: [{ type: "unknown", data: "foo" }] as any, timestamp: Date.now(), }, ]; const payload = await capturePayload({ messages }); expect(payload).toBeDefined(); const p = payload as { messages: Array<{ role: string; content: unknown[] }> }; expect(p.messages).toHaveLength(1); expect(p.messages[0].content).toEqual([{ text: "" }]); }); it("replaces blank user string content with a placeholder", async () => { const payload = await capturePayload({ messages: [{ role: "user", content: " ", timestamp: Date.now() }], }); expect(payload).toBeDefined(); const p = payload as { messages: Array<{ role: string; content: unknown[] }> }; expect(p.messages).toHaveLength(1); expect(p.messages[0].content).toEqual([{ text: "" }]); }); it("filters blank user text blocks when other content remains", async () => { const payload = await capturePayload({ messages: [ { role: "user", content: [ { type: "text", text: "" }, { type: "text", text: "hello" }, ], timestamp: Date.now(), }, ], }); expect(payload).toBeDefined(); const p = payload as { messages: Array<{ role: string; content: unknown[] }> }; expect(p.messages).toHaveLength(1); expect(p.messages[0].content).toEqual([{ text: "hello" }]); }); it("replaces user content emptied by surrogate sanitization with a placeholder", async () => { const payload = await capturePayload({ messages: [{ role: "user", content: String.fromCharCode(0xd83d), timestamp: Date.now() }], }); expect(payload).toBeDefined(); const p = payload as { messages: Array<{ role: string; content: unknown[] }> }; expect(p.messages).toHaveLength(1); expect(p.messages[0].content).toEqual([{ text: "" }]); }); it("skips assistant text blocks emptied by surrogate sanitization", async () => { const messages: Message[] = [ { role: "assistant", content: [{ type: "text", text: String.fromCharCode(0xd83d) }], api: "bedrock-converse-stream", provider: "amazon-bedrock", model: baseModel.id, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }, stopReason: "stop", timestamp: Date.now(), }, ]; const payload = await capturePayload({ messages }); expect(payload).toBeDefined(); const p = payload as { messages: Array<{ role: string; content: unknown[] }> }; expect(p.messages).toHaveLength(0); }); it("replaces blank tool result content with a placeholder", async () => { const messages: Message[] = [ { role: "toolResult", toolCallId: "tool-1", toolName: "tool", content: [{ type: "text", text: "" }], isError: false, timestamp: Date.now(), }, ]; const payload = await capturePayload({ messages }); expect(payload).toBeDefined(); const p = payload as { messages: Array<{ role: string; content: Array<{ toolResult: { content: unknown[] } }> }>; }; expect(p.messages).toHaveLength(1); expect(p.messages[0].content[0].toolResult.content).toEqual([{ text: "" }]); }); it("skips assistant messages with only unknown content blocks", async () => { const messages: Message[] = [ { role: "assistant", content: [{ type: "unknown", data: "foo" }] as any, api: "bedrock-converse-stream", provider: "amazon-bedrock", model: baseModel.id, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }, stopReason: "stop", timestamp: Date.now(), }, ]; const payload = await capturePayload({ messages }); expect(payload).toBeDefined(); const p = payload as { messages: Array<{ role: string; content: unknown[] }> }; expect(p.messages).toHaveLength(0); }); });