fix: anthropic tool-call round-trip, cache zero-hit parity, round-robin load balancing

anthropic.lua v3.0.0:
- Issue 1: tool_result/tool_use round-trip
- Issue 3: thinking default OFF (opt-in via extra_body.thinking)
- Issue 4: tool_choice mapping
- Issue 5: collect_blocks preserves unknown part types
- message_stop no longer emits done=true (was overwriting tool_calls finish_reason)
- cache_read_input_tokens normalized even at 0

gemini.lua:
- transform_response was missing cachedContentTokenCount

openai.lua (Issue 6):
- transform_error handles flat envelopes, nginx HTML, bare text

chat.go mergeUsage:
- Keep PromptTokensDetails even when CachedTokens=0

scheduler.go:
- Remove sort.SliceStable by Pref; round-robin cursor is the only LB mechanism

provider.go ModelAvailable:
- Also check Pref() > prefMin, persistently failing slots exit cands

presets.go:
- 17 built-in source templates

Tests: 6 new test functions, 2 updated for new semantics
This commit is contained in:
JianFeeeee
2026-08-28 12:02:46 +08:00
parent 94cbcb6771
commit 624fd74b45
15 changed files with 1170 additions and 114 deletions

View File

@ -136,13 +136,43 @@ end
-- 错误收敛:标准 OpenAI 信封 {error:{message,...}}
function adapter.transform_error(status, body)
-- Non-JSON body (nginx HTML error pages, plain text): extract a short
-- human-readable reason instead of letting the raw body reach the log.
local ok, resp = pcall(json.decode, body)
if not ok or type(resp) ~= "table" then return nil end
if not ok or type(resp) ~= "table" then
-- HTML error page: pull the <title> text (e.g. "413 Request Entity Too Large")
local title = string.match(body or "", "<title>(.-)</title>")
if title and title ~= "" then return title end
-- Bare text: first non-empty line, capped
local line = string.match(body or "", "^%s*([^\r\n]+)")
if line and line ~= "" and not string.match(line, "^<") then
return string.sub(line, 1, 200)
end
return nil
end
-- Standard OpenAI envelope: {error:{message,...}} or {error:"..."}
local e = resp.error
if type(e) == "table" and type(e.message) == "string" then
return e.message
end
if type(e) == "string" then return e end
-- Flat envelope used by many OpenAI-compatible gateways:
-- {"code":20012,"message":"Model does not exist..."}
-- {"code":"INVALID_API_KEY","message":"Invalid API key"}
if type(resp.message) == "string" and resp.message ~= "" then
if resp.code ~= nil then
return tostring(resp.code) .. ": " .. resp.message
end
return resp.message
end
-- Some gateways use {detail:"..."} (FastAPI style)
if type(resp.detail) == "string" and resp.detail ~= "" then
return resp.detail
end
return nil
end