Reject legacy empty function-call terminals
Why: The parser also recognizes function_call as a tool finish, but cannot execute a legacy call when no deltas were collected. It must fail closed like tool_calls. Evidence: The function_call variant failed before this guard and passed afterward; full Agent-Native vet/test passed. Impact: Inconsistent legacy tool terminals fail with returned Usage preserved; valid streams are unchanged. Refs: U3 r0.4 independent review Important 2 closure
This commit is contained in:
+1
-1
@@ -195,7 +195,7 @@ func emitParsed(ctx context.Context, r io.Reader, out chan<- Event) {
|
||||
// A finish reason alone does not prove the tool arguments arrived intact.
|
||||
// Preserve returned usage when rejecting a partial terminal, so the host
|
||||
// can charge this call and stop rather than treating it as a free retry.
|
||||
if finished == "tool_calls" && len(order) == 0 {
|
||||
if (finished == "tool_calls" || finished == "function_call") && len(order) == 0 {
|
||||
sendTerminalFailure("tool call finish reason without a tool call")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -94,8 +94,10 @@ func TestParseSSEToolCallIncrements(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseSSEToolFinishWithoutCallFails(t *testing.T) {
|
||||
for _, reason := range []string{"tool_calls", "function_call"} {
|
||||
t.Run(reason, func(t *testing.T) {
|
||||
body := strings.Join([]string{
|
||||
`data: {"choices":[{"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":12,"completion_tokens":5,"total_tokens":17}}`,
|
||||
`data: {"choices":[{"delta":{},"finish_reason":"` + reason + `"}],"usage":{"prompt_tokens":12,"completion_tokens":5,"total_tokens":17}}`,
|
||||
`data: [DONE]`,
|
||||
"",
|
||||
}, "\n")
|
||||
@@ -109,6 +111,8 @@ func TestParseSSEToolFinishWithoutCallFails(t *testing.T) {
|
||||
!end.StreamComplete || len(end.Message.ToolCalls) != 0 {
|
||||
t.Fatalf("empty tool finish must fail and retain Usage: %#v", end)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSSELengthFailsUnfinishedToolCall(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user