Merge origin/master (v0.5.69) into gitea/new_feature
This commit is contained in:
File diff suppressed because it is too large
Load Diff
49
tests/translator/claude-claude-stream-decloak.test.js
Normal file
49
tests/translator/claude-claude-stream-decloak.test.js
Normal file
@@ -0,0 +1,49 @@
|
||||
// Regression test: claude → claude streaming passthrough must still decloak
|
||||
// tool names. translateRequest() cloaks client tool names with CLAUDE_TOOL_SUFFIX
|
||||
// for OAuth-cloaked Claude providers (cloakToolsOnOAuth) even when source and
|
||||
// target formats match; the same-format fast path in translateResponse() used
|
||||
// to return chunks untouched, leaking the suffixed name (e.g. "run_code_ide")
|
||||
// to the client, which then rejected the call as an unknown tool.
|
||||
import { describe, it, expect } from "vitest";
|
||||
import "./registerAll.js";
|
||||
import { translateResponse } from "../../open-sse/translator/index.js";
|
||||
import { FORMATS } from "../../open-sse/translator/formats.js";
|
||||
import { CLAUDE_TOOL_SUFFIX } from "../../open-sse/config/appConstants.js";
|
||||
|
||||
const CLOAKED = "run_code" + CLAUDE_TOOL_SUFFIX;
|
||||
|
||||
const toolUseStart = (name) => ({
|
||||
type: "content_block_start",
|
||||
index: 1,
|
||||
content_block: { type: "tool_use", id: "toolu_01XYZ", name, input: {} }
|
||||
});
|
||||
|
||||
describe("Claude → Claude streaming passthrough (OAuth tool cloak)", () => {
|
||||
const state = { toolNameMap: new Map([[CLOAKED, "run_code"]]) };
|
||||
|
||||
it("restores the original tool name on tool_use content_block_start", () => {
|
||||
const [out] = translateResponse(FORMATS.CLAUDE, FORMATS.CLAUDE, toolUseStart(CLOAKED), state);
|
||||
expect(out.content_block.name).toBe("run_code");
|
||||
});
|
||||
|
||||
it("leaves uncloaked chunks untouched (identity passthrough)", () => {
|
||||
const chunk = toolUseStart("Bash"); // decoy name, not in the map
|
||||
const [out] = translateResponse(FORMATS.CLAUDE, FORMATS.CLAUDE, chunk, state);
|
||||
expect(out).toBe(chunk);
|
||||
|
||||
const textChunk = { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "hi" } };
|
||||
const [outText] = translateResponse(FORMATS.CLAUDE, FORMATS.CLAUDE, textChunk, state);
|
||||
expect(outText).toBe(textChunk);
|
||||
});
|
||||
|
||||
it("is a no-op when no cloak map is present", () => {
|
||||
const chunk = toolUseStart(CLOAKED);
|
||||
const [out] = translateResponse(FORMATS.CLAUDE, FORMATS.CLAUDE, chunk, {});
|
||||
expect(out).toBe(chunk);
|
||||
});
|
||||
|
||||
it("tolerates the null flush chunk", () => {
|
||||
const [out] = translateResponse(FORMATS.CLAUDE, FORMATS.CLAUDE, null, state);
|
||||
expect(out).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -58,6 +58,18 @@ describe("extractThinking", () => {
|
||||
it("no intent → null", () => {
|
||||
expect(extractThinking({ messages: [] })).toBeNull();
|
||||
});
|
||||
it("reasoning_effort wins over thinking:{type:enabled} (no budget)", () => {
|
||||
expect(extractThinking({
|
||||
thinking: { type: "enabled" },
|
||||
reasoning_effort: "high",
|
||||
})).toEqual({ mode: "level", level: "high" });
|
||||
});
|
||||
it("reasoning.effort wins over thinking:{type:enabled} (no budget)", () => {
|
||||
expect(extractThinking({
|
||||
thinking: { type: "enabled" },
|
||||
reasoning: { effort: "medium" },
|
||||
})).toEqual({ mode: "level", level: "medium" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyThinking per provider format", () => {
|
||||
@@ -70,6 +82,21 @@ describe("applyThinking per provider format", () => {
|
||||
// Sonnet 5). Both fields together are the documented adaptive shape.
|
||||
expect(out.thinking).toEqual({ type: "adaptive" });
|
||||
});
|
||||
it("claude adaptive thinking maps auto effort to a supported level", () => {
|
||||
const out = apply("claude", "claude-opus-4.7", { thinking: { type: "adaptive" } }, "claude");
|
||||
expect(out.output_config).toEqual({ effort: "high" });
|
||||
expect(out.thinking).toEqual({ type: "adaptive" });
|
||||
});
|
||||
it("permanently adaptive Claude maps auto effort without adding a thinking switch", () => {
|
||||
const out = apply("claude", "claude-fable-5-1", { thinking: { type: "adaptive" } }, "claude");
|
||||
expect(out.output_config).toEqual({ effort: "high" });
|
||||
expect(out.thinking).toBeUndefined();
|
||||
});
|
||||
it("Fable 5.1 → effort without a redundant thinking switch", () => {
|
||||
const out = apply("claude", "claude-fable-5-1", { reasoning_effort: "high" }, "claude");
|
||||
expect(out.output_config).toEqual({ effort: "high" });
|
||||
expect(out.thinking).toBeUndefined();
|
||||
});
|
||||
it("claude haiku → enabled+budget", () => {
|
||||
const out = apply("claude", "claude-haiku-4.5", { reasoning_effort: "high" }, "claude");
|
||||
expect(out.thinking).toEqual({ type: "enabled", budget_tokens: 24576 });
|
||||
@@ -114,6 +141,27 @@ describe("applyThinking per provider format", () => {
|
||||
expect(out.enable_thinking).toBe(false);
|
||||
expect(out.thinking).toBeUndefined();
|
||||
});
|
||||
it.each([
|
||||
["high", "high"],
|
||||
["max", "max"],
|
||||
["xhigh", "max"],
|
||||
["low", "low"],
|
||||
["medium", "high"],
|
||||
["minimal", "low"],
|
||||
])("GLM-5.3 %s → reasoning_effort=%s (low|high|max only, per z.ai docs)", (input, expected) => {
|
||||
const out = apply("openai", "glm-5.3", { reasoning_effort: input }, "glm-cn");
|
||||
expect(out.thinking).toEqual({ type: "enabled" });
|
||||
expect(out.reasoning_effort).toBe(expected);
|
||||
});
|
||||
it("GLM-5.2 also gets reasoning_effort (supported from 5.2 onward)", () => {
|
||||
const out = apply("openai", "glm-5.2", { reasoning_effort: "low" }, "glm-cn");
|
||||
expect(out.reasoning_effort).toBe("low");
|
||||
});
|
||||
it("GLM-4.7 (pre-5.2) does not get reasoning_effort — z.ai ignores it", () => {
|
||||
const out = apply("openai", "glm-4.7", { reasoning_effort: "low" }, "glm-cn");
|
||||
expect(out.thinking).toEqual({ type: "enabled" });
|
||||
expect(out.reasoning_effort).toBeUndefined();
|
||||
});
|
||||
it("Qwen on → enable_thinking + thinking_budget", () => {
|
||||
const out = apply("openai", "qwen3-max", { reasoning_effort: "medium" }, "qwen");
|
||||
expect(out.enable_thinking).toBe(true);
|
||||
@@ -182,6 +230,26 @@ describe("applyThinking per provider format", () => {
|
||||
const out = apply("openai", "gpt-5.6-sol", { reasoning_effort: "max" }, "kiro");
|
||||
expect(out.reasoning_effort).toBe("xhigh");
|
||||
});
|
||||
it.each([
|
||||
["gemini-3.5-flash-lite"],
|
||||
["gemini-3.7-flash"],
|
||||
["gemini-3-pro"],
|
||||
])("Gemini 3.x model %s (gemini-level) over a custom OpenAI-compatible provider → reasoning_effort, not generationConfig (regression: #3718)", (model) => {
|
||||
const out = apply("openai", model, { reasoning_effort: "medium" }, "my-custom-gemini-openai");
|
||||
expect(out.reasoning_effort).toBe("medium");
|
||||
expect(out.generationConfig).toBeUndefined();
|
||||
expect(out.thinkingConfig).toBeUndefined();
|
||||
});
|
||||
it("Gemini 2.5 model (gemini-budget) over a custom OpenAI-compatible provider → reasoning_effort, not generationConfig (regression: #3718)", () => {
|
||||
const out = apply("openai", "gemini-2.5-flash", { reasoning_effort: "high" }, "my-custom-gemini-openai");
|
||||
expect(out.reasoning_effort).toBe("high");
|
||||
expect(out.generationConfig).toBeUndefined();
|
||||
expect(out.thinkingConfig).toBeUndefined();
|
||||
});
|
||||
it("Gemini model over its native format (antigravity/gemini-cli/vertex) still gets generationConfig", () => {
|
||||
const out = apply("gemini-cli", "gemini-3.5-flash-lite", { reasoning_effort: "medium" }, "gemini-cli");
|
||||
expect(out.generationConfig.thinkingConfig.thinkingLevel).toBe("medium");
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractReasoningText (response shapes)", () => {
|
||||
|
||||
Reference in New Issue
Block a user