Merge remote-tracking branch 'origin/master' into gitea/feature/end

Resolved conflicts taking origin/master (v0.5.55) as canonical, with local
features re-applied:
- runtime log level (LOG_LEVEL env + dashboard Settings → Logging, applied
  immediately and persisted across restarts)
- free/noAuth provider enable/disable toggle via providerStrategies.enabled
- parallel model testing (Test All Models / Test Selected Keys)
This commit is contained in:
2026-08-17 00:38:31 +07:00
parent e7470e955e
commit 7e45ead2ac
557 changed files with 53396 additions and 6935 deletions

View File

@@ -67,6 +67,122 @@ describe("OpenAI → Claude context mapping", () => {
expect(JSON.stringify(out), "remote image dropped").toContain("pic.png");
});
// claude.js hasValidContent() — a user message whose content is only an
// image (no text block) was filtered out by prepareClaudeRequest's
// "drop empty messages" pass, so an image-only turn (e.g. a vision
// describe request with no accompanying prompt text in the user message)
// produced an empty `messages` array and Anthropic rejected the request
// with "messages: at least one message is required".
it("user message with only an image is not dropped as empty", () => {
const out = T({
messages: [
{ role: "system", content: "Describe the image." },
{ role: "user", content: [
{ type: "image_url", image_url: { url: "data:image/png;base64,AAAA" } },
] },
],
});
expect(out.messages.length, "image-only user message was dropped").toBeGreaterThan(0);
expect(out.messages[0].content).toEqual(
expect.arrayContaining([expect.objectContaining({ type: "image" })])
);
});
// prepareClaudeRequest reconciles max_tokens vs thinking.budget_tokens.
// applyThinking runs after adjustMaxTokens caps max_tokens, so a claude-budget
// model at "max" effort (budget 128000) can exceed the clamped max_tokens and
// trip Anthropic's "max_tokens > budget_tokens" rule (400). See claude.js.
describe("max_tokens vs thinking.budget_tokens reconciliation", () => {
// 64k-ceiling model (maxOutput 64000) + max-effort budget 128000: budget alone
// exceeds the ceiling → cap max_tokens at 64000 and shrink budget below it.
it("max effort budget on a 64k model → budget < max_tokens ≤ 64000", () => {
const out = prepareClaudeRequest({
model: "claude-opus-4-20250514",
max_tokens: 64000,
thinking: { type: "enabled", budget_tokens: 128000 },
messages: [{ role: "user", content: "q" }],
}, "anthropic");
expect(out.max_tokens).toBe(64000);
expect(out.thinking.budget_tokens).toBeLessThan(out.max_tokens);
expect(out.thinking.budget_tokens).toBeGreaterThan(0);
});
// Budget fits under the ceiling but exceeds a small client max_tokens →
// raise max_tokens to fit, preserving the requested thinking depth.
it("xhigh budget with a low client max_tokens → raise max_tokens, preserve budget", () => {
const out = prepareClaudeRequest({
model: "claude-opus-4-20250514",
max_tokens: 16000,
thinking: { type: "enabled", budget_tokens: 32768 },
messages: [{ role: "user", content: "q" }],
}, "anthropic");
expect(out.thinking.budget_tokens).toBe(32768);
expect(out.max_tokens).toBe(33792); // 32768 + 1024, under the 64000 ceiling
});
// Budget already below max_tokens → nothing to reconcile.
it("high budget under max_tokens → both unchanged", () => {
const out = prepareClaudeRequest({
model: "claude-opus-4-20250514",
max_tokens: 64000,
thinking: { type: "enabled", budget_tokens: 24576 },
messages: [{ role: "user", content: "q" }],
}, "anthropic");
expect(out.max_tokens).toBe(64000);
expect(out.thinking.budget_tokens).toBe(24576);
});
// Non-budget thinking shapes (adaptive / disabled) carry no budget_tokens →
// the reconciliation must never touch them.
it("adaptive thinking (no budget_tokens) is left untouched", () => {
const out = prepareClaudeRequest({
model: "claude-opus-4-20250514",
max_tokens: 64000,
thinking: { type: "adaptive" },
messages: [{ role: "user", content: "q" }],
}, "anthropic");
expect(out.max_tokens).toBe(64000);
expect(out.thinking).toEqual({ type: "adaptive" });
});
// Lifted ceiling: a claude-budget model whose caps declare maxOutput 128000
// (e.g. fable) may use the full budget at max effort instead of being pinned
// to the conservative 64000 default.
it("max effort budget on a 128k model → max_tokens up to 128000, budget preserved just under", () => {
const out = prepareClaudeRequest({
model: "claude-fable-5",
max_tokens: 64000,
thinking: { type: "enabled", budget_tokens: 128000 },
messages: [{ role: "user", content: "q" }],
}, "anthropic");
expect(out.max_tokens).toBe(128000);
expect(out.thinking.budget_tokens).toBe(126976); // 128000 - 1024
expect(out.thinking.budget_tokens).toBeLessThan(out.max_tokens);
});
// Regression: a default 64k-ceiling model still clamps an over-large client
// max_tokens down to 64000 (the lift is per-model, not global).
it("over-large client max_tokens on a 64k model is still clamped to 64000", () => {
const out = prepareClaudeRequest({
model: "claude-opus-4-20250514",
max_tokens: 120000,
messages: [{ role: "user", content: "q" }],
}, "anthropic");
expect(out.max_tokens).toBe(64000);
});
// Lifted ceiling for a 128k model: a large client max_tokens is now allowed
// through instead of being clamped to 64000.
it("large client max_tokens on a 128k model is allowed up to maxOutput", () => {
const out = prepareClaudeRequest({
model: "claude-fable-5",
max_tokens: 100000,
messages: [{ role: "user", content: "q" }],
}, "anthropic");
expect(out.max_tokens).toBe(100000);
});
});
it("DeepSeek Claude transport adds a thinking placeholder before tool_use in thinking mode", () => {
const out = prepareClaudeRequest({
model: "deepseek-v4-pro",