Merge origin/master (v0.5.69) into gitea/new_feature

This commit is contained in:
2026-09-07 14:10:11 +07:00
222 changed files with 12285 additions and 3371 deletions

File diff suppressed because it is too large Load Diff

View 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();
});
});

View File

@@ -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)", () => {