From: Stefan Gasser Date: Tue, 7 Jul 2026 12:25:42 +0000 (+0200) Subject: Accept Claude Code system role messages X-Git-Tag: v0.7.6^0 X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=1788445aae7202f3a6f05b89ca9522447409a805;p=sgasser-llm-shield.git Accept Claude Code system role messages --- diff --git a/docs/api-reference/status.mdx b/docs/api-reference/status.mdx index 184cc08..03647ba 100644 --- a/docs/api-reference/status.mdx +++ b/docs/api-reference/status.mdx @@ -66,7 +66,7 @@ curl http://localhost:3000/info ```json { "name": "PasteGuard", - "version": "0.7.5", + "version": "0.7.6", "description": "Privacy proxy for LLMs", "mode": "mask", "providers": { diff --git a/package.json b/package.json index b609eda..c786944 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pasteguard", - "version": "0.7.5", + "version": "0.7.6", "description": "Privacy proxy for LLMs. Masks personal data and secrets before sending to your provider.", "type": "module", "main": "src/index.ts", diff --git a/src/masking/extractors/anthropic.test.ts b/src/masking/extractors/anthropic.test.ts index cb49906..11576ea 100644 --- a/src/masking/extractors/anthropic.test.ts +++ b/src/masking/extractors/anthropic.test.ts @@ -68,6 +68,24 @@ describe("Anthropic Text Extractor", () => { }); }); + test("extracts Claude Code system role messages", () => { + const request = createRequest([ + { role: "user", content: "Hello" }, + { role: "system", content: "Tool context reminder" }, + ]); + + const spans = anthropicExtractor.extractTexts(request); + + expect(spans).toHaveLength(2); + expect(spans[1]).toEqual({ + text: "Tool context reminder", + path: "messages[1].content", + messageIndex: 1, + partIndex: 0, + role: "system", + }); + }); + test("extracts text from system array", () => { const request = createRequest( [{ role: "user", content: "Hello" }], diff --git a/src/providers/anthropic/types.ts b/src/providers/anthropic/types.ts index 69f6d4f..f8e3049 100644 --- a/src/providers/anthropic/types.ts +++ b/src/providers/anthropic/types.ts @@ -77,7 +77,9 @@ export const ContentBlockSchema = z.discriminatedUnion("type", [ // Message and request types export const AnthropicMessageSchema = z .object({ - role: z.enum(["user", "assistant"]), + // Claude Code can send system reminders as messages when tools are enabled. + // Preserve them so PasteGuard stays transparent instead of rejecting upstream traffic. + role: z.enum(["user", "assistant", "system"]), content: z.union([z.string(), z.array(ContentBlockSchema)]), }) .passthrough(); diff --git a/src/routes/anthropic.test.ts b/src/routes/anthropic.test.ts index 663b6c4..be327ed 100644 --- a/src/routes/anthropic.test.ts +++ b/src/routes/anthropic.test.ts @@ -43,6 +43,24 @@ describe("POST /anthropic/v1/messages", () => { expect(res.status).toBe(400); }); + test("accepts Claude Code system role messages", () => { + const result = AnthropicRequestSchema.safeParse({ + model: "claude-opus-4-8", + max_tokens: 64000, + stream: true, + system: [{ type: "text", text: "Default system prompt" }], + messages: [ + { role: "user", content: [{ type: "text", text: "Hello" }] }, + { role: "system", content: "Tool context reminder" }, + ], + }); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.messages[1].role).toBe("system"); + } + }); + test("returns 400 for missing model", async () => { const res = await app.request("/anthropic/v1/messages", { method: "POST",