main had fallen a full feature generation behind: 13739 lines and 29 files
that main did not contain at all (the whole Lua plugin mechanism, the billing
package, stats_period, plugins_api, docs/plugins.md). v1.7.x contains
everything main has and nothing main lacks, so this is a one-way merge rather
than two divergent lines.
Eight conflicts, each read before resolving — the script that does it lives at
.probe/resolve_merge_conflicts.py and aborts rather than guess:
* cmd/gui/package.json, cmd/gui/package-lock.json — main already carries 1.8.0
(4b37e1a); the release line carries 1.7.6. Kept 1.8.0. Taking 1.7.6 would
ship tag v1.8.0 next to a manifest claiming 1.7.6.
* packaging/config.example.yaml (2 hunks) — release side is a superset both
times: it documents the `auto:` field, and it replaces the older one-line
runtime_file note with a paragraph that also states where templates and
deleted-markers live and warns that the AUTO chain, gateway keys and sources
are in config.yaml. HEAD's wording is strictly less.
* internal/config/config.go, internal/core/core.go, internal/gateway/api.go,
internal/gateway/apiv1.go, internal/gateway/stats.go (6 hunks) — HEAD is
zero-length in every one and the release side is the new code: BillingDSL
types, applyBillingDSL, the pointer-semantics source upsert, the
optional_fields doc string, the AUTO chain-walk field. The resolver asserts
the empty-HEAD property instead of assuming it.
Verified before committing: no residual conflict markers, both manifests read
1.8.0, `go build -tags luajit ./...` clean, `go test -tags luajit ./...` all nine
packages pass.
Four adapters handled tool_calls in transform_stream_chunk but lost them in
transform_response, so any NON-streaming tool-using conversation broke on its
second request: the client received finish_reason:"tool_calls" with no
tool_calls payload, replayed an assistant message whose function
name/arguments were empty, and the upstream rejected the next turn with
400 invalid tool_call function, function/name/arguments cannot be empty
The production audit trail shows 46 such failures on sensenova alone.
- sensenova.lua: forward message.tool_calls, decoding the arguments JSON string
into an object as the unified shape expects.
- gemini.lua: collect functionCall parts from candidates[].content.parts. Also
correct finish_reason, since Gemini reports "STOP" even when it emitted a
function call and clients keyed on it treat that as a finished answer.
- ollama.lua: the field was initialized to an empty table and never filled;
fill it and likewise correct done_reason "stop" -> "tool_calls".
trae is a different failure with the same symptom: trae-local-api's OpenAI
endpoint (/v1/chat/completions, src/server.js:353) never reads the request's
`tools` array — only its Anthropic endpoint does — so the relayed model is never
told the tool schema and instead PRINTS a <tool_call>{...}</tool_call> block into
content, leaving message.tool_calls null and finish_reason "stop". An OpenAI
client sees an ordinary completion and its agent loop ends mid-conversation.
trae.lua now recovers the structured call from that text, strips the block from
user-visible content, and corrects finish_reason. Both tag spellings
(<tool_call>/<toolcall>, the latter is what the same codebase's Anthropic prompt
asks for) and all three argument key names (arguments/params/input) are accepted.
This is a defensive fallback: fixing the upstream shim to honour `tools` remains
the real fix, since the model still guesses parameter names.
Tests: TestNonStreamToolCallsPreserved covers all ten OpenAI-shaped adapters,
TestGeminiNonStreamToolCalls and TestOllamaNonStreamToolCalls cover their native
shapes, TestTraeTextToolCallRecovery covers both tag spellings, prose around the
block, and asserts a plain text answer never gains tool_calls.
Verified end-to-end against mock upstreams reproducing each shape: a full
two-round agent loop (tool call -> tool result -> final answer) now completes for
both the structured and the text-emitted variants.
README: new "冷却与半冷却探测(自愈调度)" section with the per-class cooldown
table (5xx exponential to 5min / 401-403 10min / 429 30s fixed / quota aligned
to its window), the probe-slot formula and the ordering rule that probes are
tried last. The LuaJIT section now says the pool is an elastic ceiling rather
than a preallocation and documents both step formulas. Headline figures replaced
with measured ones: idle ~10 MB, ~19 MB starting with a 29 MB audit log, and a
new bullet for on-demand log loading.
package.json also loses a `\u2014` escape and the broken indentation that an
earlier edit left in the electron-builder block.