diff --git a/open-sse/handlers/chatCore/claudeMessageTypes.ts b/open-sse/handlers/chatCore/claudeMessageTypes.ts index 24492a3957..ad959db8f5 100644 --- a/open-sse/handlers/chatCore/claudeMessageTypes.ts +++ b/open-sse/handlers/chatCore/claudeMessageTypes.ts @@ -7,9 +7,19 @@ * shapes the handler already used inline; behaviour is unchanged. */ -export type ClaudeContentBlock = Record; +export type ClaudeContentBlock = { + type?: string; + text?: string; + name?: string; + tool_use_id?: string; + cache_control?: unknown; + signature?: string; + thinking?: string; + [key: string]: unknown; +}; export type ClaudeMessage = { - role?: unknown; - content?: unknown; + role?: string; + content?: string | ClaudeContentBlock[]; + [key: string]: unknown; }; diff --git a/tests/unit/chatcore-claude-upstream-messages.test.ts b/tests/unit/chatcore-claude-upstream-messages.test.ts index ddf1892b44..1196a4180a 100644 --- a/tests/unit/chatcore-claude-upstream-messages.test.ts +++ b/tests/unit/chatcore-claude-upstream-messages.test.ts @@ -10,6 +10,7 @@ import { extractSystemMessagesToBody, normalizeClaudeUpstreamMessages, } from "../../open-sse/handlers/chatCore/claudeUpstreamMessages.ts"; +import type { ClaudeMessage } from "../../open-sse/handlers/chatCore/claudeMessageTypes.ts"; test("extractSystemMessagesToBody lifts system/developer roles into top-level system", () => { const payload: Record = { @@ -51,7 +52,13 @@ test("extractSystemMessagesToBody is a no-op without system messages or a messag test("normalizeClaudeUpstreamMessages drops empty text blocks", () => { const payload: Record = { messages: [ - { role: "user", content: [{ type: "text", text: "" }, { type: "text", text: "keep" }] }, + { + role: "user", + content: [ + { type: "text", text: "" }, + { type: "text", text: "keep" }, + ], + }, ], }; normalizeClaudeUpstreamMessages(payload); @@ -59,6 +66,19 @@ test("normalizeClaudeUpstreamMessages drops empty text blocks", () => { assert.deepEqual(msg.content, [{ type: "text", text: "keep" }]); }); +test("shared Claude message contract preserves structured content", () => { + const message: ClaudeMessage = { + role: "user", + content: [{ type: "text", text: "keep", cache_control: { type: "ephemeral" } }], + metadata: { source: "test" }, + }; + const payload: Record = { messages: [message] }; + + normalizeClaudeUpstreamMessages(payload); + + assert.deepEqual(payload.messages, [message]); +}); + test("normalizeClaudeUpstreamMessages collapses tool_result to text by default", () => { const payload: Record = { messages: [