mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 08:57:57 +00:00
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:
@ -422,3 +422,35 @@ func TestOpenCodeAdapterKeepsWhitelistedRolesAndDropsMultimodal(t *testing.T) {
|
||||
t.Fatalf("unexpected roles: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdaptersPassFinishReason(t *testing.T) {
|
||||
vm := NewVM(freshAdapterDir(t))
|
||||
if err := vm.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer vm.Stop()
|
||||
// OpenAI-style chunk with a real finish reason must surface finish_reason
|
||||
// and done=true; an empty-string finish_reason (sensenova sends "" on
|
||||
// every chunk) must NOT terminate the stream.
|
||||
chunk := `{"choices":[{"index":0,"finish_reason":"tool_calls","delta":{"content":""}}]}`
|
||||
empty := `{"choices":[{"index":0,"finish_reason":"","delta":{"content":"x"}}]}`
|
||||
for _, name := range []string{"openai", "deepseek", "github", "groq", "kimicode", "mistral", "opencode"} {
|
||||
out, err := vm.Transform(name, "transform_stream_chunk", chunk)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
if !strings.Contains(out, `"finish_reason":"tool_calls"`) {
|
||||
t.Fatalf("%s: finish_reason lost: %s", name, out)
|
||||
}
|
||||
if !strings.Contains(out, `"done":true`) {
|
||||
t.Fatalf("%s: done not set on real finish: %s", name, out)
|
||||
}
|
||||
out, err = vm.Transform(name, "transform_stream_chunk", empty)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
if strings.Contains(out, `"done":true`) {
|
||||
t.Fatalf("%s: empty finish_reason must not end stream: %s", name, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user