From 244ba22184ac8f52ebe08318d38fc942f6a38fcd Mon Sep 17 00:00:00 2001 From: OmniRoute Ops Date: Tue, 12 May 2026 09:03:05 +0000 Subject: [PATCH] fix(cliproxyapi): detect Anthropic shape on minimal Capy bodies Discovered post-deploy: simple Capy /v1/messages requests (string content, no system block) were misdetected as OpenAI-shape and routed to /v1/chat/completions instead of /v1/messages. CPA then responded with chat.completion shape, leaking OpenAI shape to Anthropic SDK clients and skipping the Anthropic CC wire-image cloak. Strengthen isAnthropicShape with two more strong signals (any one is decisive): - top-level `thinking` field (Anthropic-only; OpenAI uses `reasoning`) - top-level `metadata.user_id` (CC wire-image OAuth identifier) These survive even on minimal bodies where messages[0].content is a string and no system block is present. Co-Authored-By: Claude Opus 4.7 (1M context) --- open-sse/executors/cliproxyapi.ts | 14 ++++++++++++++ tests/unit/cliproxyapi-executor.test.ts | 15 +++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/open-sse/executors/cliproxyapi.ts b/open-sse/executors/cliproxyapi.ts index 601d100e037c..df14e8ff80ee 100644 --- a/open-sse/executors/cliproxyapi.ts +++ b/open-sse/executors/cliproxyapi.ts @@ -178,6 +178,20 @@ export class CliproxyapiExecutor extends BaseExecutor { // entry inside messages[], not at body level. Accept both string and // array-of-content-blocks forms (Anthropic supports both per the docs). if (b.system !== undefined) return true; + // Strong signal: top-level `thinking` field is Anthropic-only. OpenAI + // uses `reasoning` / `reasoning_effort`. Even adaptive/raw Capy shapes + // emit thinking, so this catches minimal Capy bodies (string content, + // no system block) that would otherwise miss the messages[0].content + // array check below. + if (b.thinking !== undefined) return true; + // Strong signal: top-level `metadata.user_id` is the CC wire-image + // identifier; OpenAI request bodies don't carry it. + if ( + b.metadata && + typeof b.metadata === "object" && + (b.metadata as Record).user_id !== undefined + ) + return true; // Strong signal: messages[0].content is an array of Anthropic content blocks const msgs = b.messages; if (Array.isArray(msgs) && msgs.length > 0) { diff --git a/tests/unit/cliproxyapi-executor.test.ts b/tests/unit/cliproxyapi-executor.test.ts index 0d65bf243134..a66c39f9c575 100644 --- a/tests/unit/cliproxyapi-executor.test.ts +++ b/tests/unit/cliproxyapi-executor.test.ts @@ -178,15 +178,22 @@ describe("CliproxyapiExecutor", () => { assert.equal(result.thinking, undefined); }); - it("does not touch thinking on OpenAI-shape bodies", () => { + it("does not strip OpenAI-shape bodies (no thinking, no system, string content)", () => { const exec = new CliproxyapiExecutor(); const body = { model: "gpt-5.5", - messages: [{ role: "user", content: "hi" }], - thinking: { type: "adaptive", display: "summarized" }, + messages: [ + { role: "system", content: "be brief" }, + { role: "user", content: "hi" }, + ], + reasoning_effort: "medium", }; const result = exec.transformRequest("gpt-5.5", body, true, {}); - assert.deepEqual(result.thinking, { type: "adaptive", display: "summarized" }); + // No Anthropic indicators present, so strips are skipped. Body + // passes through with its OpenAI fields preserved. + assert.equal(result.reasoning_effort, "medium"); + assert.equal(result.thinking, undefined); + assert.equal(result.output_config, undefined); }); });