fix(providers): iterate tool-call accumulators by map, not index

ChatStream parsed the tool-call accumulator map with a positional loop
(for i := 0; i < len(accumulators); i++ { acc := accumulators[i] }),
assuming OpenAI-compatible providers always emit contiguous zero-based
tc.Index values in streamed delta.tool_calls.

Some OpenAI-compatible gateways (observed with a self-hosted point-p1
proxy) emit non-contiguous or non-zero-based indices. When that
happens accumulators[i] misses a populated key and returns a nil
*toolCallAccumulator, and the next line dereferences acc.rawArgs on
that nil pointer, causing a SIGSEGV panic. The panic kills the
in-flight chat run's goroutine; the gateway's tracing safety-net logs
'finalizing orphan trace' and the user's message goes unanswered.

Fix: range over the accumulators map directly instead of indexing by
position, and skip nil entries defensively.

Verified: built a full backend image (make build-full equivalent,
CGO_ENABLED=0 go build -tags embedui) from this branch, deployed it
against a real point-p1-backed agent, and confirmed multiple
multi-tool-call turns (use_skill, read_file, exec x3) that previously
triggered the panic now complete via v3.run.completed with no panic
and no safety-net trace.
This commit is contained in:
aikiata committed 2026-09-06 23:20:17 +07:00
1 parent 169e0bafaf
commit db9600054c
1 file changed
+9 -3
+9 -3
View File
@@ -195,9 +195,15 @@ func (p *OpenAIProvider) ChatStream(ctx context.Context, req ChatRequest, onChun
return result, fmt.Errorf("%s: stream read error: %w", p.name, err)
}
// Parse accumulated tool call arguments
for i := 0; i < len(accumulators); i++ {
acc := accumulators[i]
// Parse accumulated tool call arguments.
// Iterate the map directly (not 0..len(accumulators)-1): providers are not
// guaranteed to emit contiguous zero-based tc.Index values in delta.ToolCalls,
// so indexing by position can miss populated slots and hit a nil accumulator,
// causing a nil-pointer panic (observed with the point-p1/9router provider).
for _, acc := range accumulators {
if acc == nil {
continue
}
args := make(map[string]any)
if err := json.Unmarshal([]byte(acc.rawArgs), &args); err != nil && acc.rawArgs != "" {
slog.Warn("openai_stream: failed to parse tool call arguments",