Skip to content

support partial tool calls#22

Merged
x2d7 merged 13 commits intodevfrom
19-assembling-tool-call
Feb 19, 2026
Merged

support partial tool calls#22
x2d7 merged 13 commits intodevfrom
19-assembling-tool-call

Conversation

@x2d7
Copy link
Owner

@x2d7 x2d7 commented Feb 19, 2026

Summary

This PR fixes tool call assembly from streaming chunks in Session.

Previously, providers that stream tool calls in multiple partial events (first chunk contains CallID, subsequent chunks contain only argument fragments) caused EventNewToolCall events to be forwarded individually — resulting in broken, incomplete argument JSON being delivered to the caller.

Now, Session accumulates partial tool call chunks and assembles them into complete events before sending them downstream.


Changes

1. Partial tool call assembly in Session

case EventNewToolCall:
    // if callid is present — it's the start of a new tool call
    if event.CallID != "" {
        approval.Attach(&event)
        toolCalls = append(toolCalls, event)
    } else {
        // add token to the last tool call
        toolCalls[len(toolCalls)-1].Content += event.Content
    }

This ensures:

  • The first chunk (with CallID) initializes a new tool call entry.
  • Subsequent chunks (without CallID) are appended to the last open tool call's Content.
  • The assembled, complete EventNewToolCall is sent only after EventCompletionEnded.

2. Removed redundant event modification logic

The modifiedEvent pattern was replaced with a unified send() helper and skipEvent flag.

3. Added send() helper

send := func(event StreamEvent) bool {
    if event == nil {
        if ctx.Err() != nil {
            return false
        }
        return true
    }
    select {
    case result <- event:
        return true
    case <-ctx.Done():
        return false
    }
}

Eliminates duplicated select blocks and handles context cancellation uniformly.


Tests

chat

New tests added in chat/chat_test.go:

Context cancellation:

  • TestComplete_ContextCancelledDuringStream — cancellation while streaming tokens
  • TestSession_ContextCancelledDuringTokenCollection — cancellation before completion arrives
  • TestSession_ContextCancelledWhileWaitingForApproval — cancellation before user resolves a tool call
  • TestSession_ContextCancelledBetweenRounds — cancellation between completion rounds

Tool call assembly:

  • TestSession_ToolCallAssembly_Basic — single tool call assembled from 3 chunks
  • TestSession_ToolCallAssembly_MultipleToolsWithAssembly — 3 tool calls (2 assembled, 1 complete) in one completion
  • TestSession_ToolCallAssembly_LargeContent — 20+ chunk assembly into a single tool call

Closes #19

@x2d7 x2d7 added the bug label Feb 19, 2026
@x2d7
Copy link
Owner Author

x2d7 commented Feb 19, 2026

Идея interlude — максимальная "стримовость" событий.
Требуется изменить логику — события вызовов должны передаваться в result сразу после окончания формирования, а не отправляться батчем перед самым окончанием генерации.

@x2d7
Copy link
Owner Author

x2d7 commented Feb 19, 2026

Fixed. Tool call events are now sent to result as soon as they are fully assembled — i.e. when the next chunk with a non-empty CallID arrives (signaling the start of a new tool call) or when EventCompletionEnded is received. Batching before end of generation is gone.

Also did a round of refactoring to keep the growing Session logic manageable:

  • Session state extracted into a dedicated sessionState struct
  • lastToolCall flush logic moved into a dedicated method
  • handleCompletionEnd extracted as a method
  • send helper and client moved into sessionState
  • Empty choices in OpenAI streaming responses are now handled gracefully

Added a test for interleaved token and tool call event ordering (TestSession_InterleavedTokensAndToolCalls) to cover the updated dispatch behavior.

@x2d7 x2d7 merged commit 04a3d21 into dev Feb 19, 2026
1 check passed
@x2d7 x2d7 deleted the 19-assembling-tool-call branch February 19, 2026 20:26
@x2d7 x2d7 mentioned this pull request Feb 19, 2026
@x2d7 x2d7 added the tools label Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant