feat(gateway): pass through upstream finish_reason end-to-end

The gateway hardcoded "stop" on every terminating stream chunk, so
tool-call rounds reported finish_reason=stop and length caps were
invisible to clients. UnifiedChunk now carries finish_reason; adapters
emit it (with empty-string finish reasons like sensenova treated as
non-terminal), standardSSEChunk passes it through for un-adapted
upstreams, [DONE] no longer emits a duplicate reason-less done chunk,
and both streaming paths emit the real reason with "stop" as fallback.

Also vendor sensenova/agentrouter adapters into the repo: they were
WebUI-only uploads and a deploy sync silently removed them while live
AUTO-chain slots still referenced them.
This commit is contained in:
JianFeeeee
2026-08-24 15:05:50 +08:00
parent 9c99ded8c0
commit bb3af3bdb3
17 changed files with 399 additions and 27 deletions

View File

@ -124,12 +124,23 @@ function adapter.transform_stream_chunk(raw_chunk)
uses = { prompt = p, completion = c, total = p + c }
end
end
local done = (chunk.delta and chunk.delta.stop_reason ~= nil)
local finish = nil
if chunk.delta and chunk.delta.stop_reason ~= nil then
-- Anthropic stop_reason -> OpenAI finish_reason
local sr = chunk.delta.stop_reason
if sr == "max_tokens" then
finish = "length"
elseif sr == "tool_use" then
finish = "tool_calls"
else
finish = "stop"
end
end
if uses ~= nil then
-- completion is final here; prompt is merged from message_start
return json.encode({ content = "", done = done, usage = uses })
return json.encode({ content = "", done = (finish ~= nil), finish_reason = finish, usage = uses })
end
return json.encode({ content = "", done = done })
return json.encode({ content = "", done = (finish ~= nil), finish_reason = finish })
end
if chunk.type == "content_block_start" and chunk.content_block
and chunk.content_block.type == "tool_use" then