fix(claude): decloak tool names in same-format streaming passthrough
translateResponse() short-circuited untouched on claude->claude streaming, so OAuth-cloaked tool names (CLAUDE_TOOL_SUFFIX) leaked to the client and every tool call was rejected as unknown. Add decloakStreamChunk(), the streaming counterpart of decloakToolNames(), and call it on the same-format path using the already-plumbed state.toolNameMap.
This commit is contained in:
@@ -3,10 +3,11 @@
|
||||
*
|
||||
* Tests cover:
|
||||
* - cloakClaudeTools() - tool renaming and forced tool_choice suffixing
|
||||
* - decloakStreamChunk() - restoring tool names in streamed Claude SSE events
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { cloakClaudeTools } from "../../open-sse/utils/claudeCloaking.js";
|
||||
import { cloakClaudeTools, decloakStreamChunk } from "../../open-sse/utils/claudeCloaking.js";
|
||||
import { CLAUDE_TOOL_SUFFIX } from "../../open-sse/config/appConstants.js";
|
||||
|
||||
describe("cloakClaudeTools", () => {
|
||||
@@ -74,3 +75,44 @@ describe("cloakClaudeTools", () => {
|
||||
expect(toolNameMap).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("decloakStreamChunk", () => {
|
||||
// Cloaked exactly as cloakClaudeTools() does on the request side
|
||||
const toolNameMap = new Map([["run_code" + CLAUDE_TOOL_SUFFIX, "run_code"]]);
|
||||
|
||||
const toolUseStart = (name) => ({
|
||||
type: "content_block_start",
|
||||
index: 1,
|
||||
content_block: { type: "tool_use", id: "toolu_01abc", name, input: {} }
|
||||
});
|
||||
|
||||
it("restores the original name on a tool_use content_block_start", () => {
|
||||
const out = decloakStreamChunk(toolUseStart("run_code" + CLAUDE_TOOL_SUFFIX), toolNameMap);
|
||||
expect(out.content_block.name).toBe("run_code");
|
||||
});
|
||||
|
||||
it("does not mutate the input chunk", () => {
|
||||
const chunk = toolUseStart("run_code" + CLAUDE_TOOL_SUFFIX);
|
||||
decloakStreamChunk(chunk, toolNameMap);
|
||||
expect(chunk.content_block.name).toBe("run_code" + CLAUDE_TOOL_SUFFIX);
|
||||
});
|
||||
|
||||
it("passes through names the map does not know (e.g. decoy tools)", () => {
|
||||
const chunk = toolUseStart("Bash");
|
||||
expect(decloakStreamChunk(chunk, toolNameMap)).toBe(chunk);
|
||||
});
|
||||
|
||||
it("passes through non-tool_use events unchanged", () => {
|
||||
const textStart = { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } };
|
||||
expect(decloakStreamChunk(textStart, toolNameMap)).toBe(textStart);
|
||||
|
||||
const delta = { type: "content_block_delta", index: 1, delta: { type: "input_json_delta", partial_json: "{}" } };
|
||||
expect(decloakStreamChunk(delta, toolNameMap)).toBe(delta);
|
||||
});
|
||||
|
||||
it("tolerates null chunks and missing maps (stream flush path)", () => {
|
||||
expect(decloakStreamChunk(null, toolNameMap)).toBeNull();
|
||||
expect(decloakStreamChunk(toolUseStart("run_code" + CLAUDE_TOOL_SUFFIX), null).content_block.name).toBe("run_code" + CLAUDE_TOOL_SUFFIX);
|
||||
expect(decloakStreamChunk(toolUseStart("run_code" + CLAUDE_TOOL_SUFFIX), new Map()).content_block.name).toBe("run_code" + CLAUDE_TOOL_SUFFIX);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user