CollectStream is for callers who want streaming's early-first-byte behaviour
without handling events — a progress spinner, or a timeout guard on a long
generation.
Reference#
Parameters
s— the stream to drain. Always closed, including on the error path.provider,model— stamped onto the assembled response. The events themselves do not carry them.
Returns
A *Response assembled from the events, or the stream's error.
Caveats
- The returned
Responsehas noRaw— it is assembled from events, not from one provider body. - It blocks until the stream is finished. If you want to display tokens as they arrive, this is the wrong tool; write the loop.
- It infers
StopToolUsewhen calls were seen but no stop reason arrived. - It always closes the stream, so you cannot leak one by forgetting.
What it assembles#
- Every
EventTextDeltaconcatenated into oneTextpart. - Every
EventToolCallappended as aToolCallpart, in order. UsageandStopReasonfrom the terminalEventDone.
Usage#
Draining a stream
stream, err := client.Stream(ctx, req)
if err != nil {
return err
}
// No defer needed: CollectStream always closes it.
resp, err := skyl.CollectStream(stream, "openai", "gpt-5.6")
if err != nil {
return err
}
fmt.Println(resp.Text(), resp.StopReason, resp.Usage.TotalTokens())stream, err := client.Stream(ctx, req)
if err != nil {
return err
}
// No defer needed: CollectStream always closes it.
resp, err := skyl.CollectStream(stream, "openai", "gpt-5.6")
if err != nil {
return err
}
fmt.Println(resp.Text(), resp.StopReason, resp.Usage.TotalTokens())Uniform code paths
// Return one *Response whether or not this call streamed.
func ask(ctx context.Context, c *skyl.Client, req *skyl.Request, stream bool) (*skyl.Response, error) {
if !stream {
return c.Complete(ctx, req)
}
s, err := c.Stream(ctx, req)
if err != nil {
return nil, err
}
return skyl.CollectStream(s, c.Provider().Name(), req.Model)
}// Return one *Response whether or not this call streamed.
func ask(ctx context.Context, c *skyl.Client, req *skyl.Request, stream bool) (*skyl.Response, error) {
if !stream {
return c.Complete(ctx, req)
}
s, err := c.Stream(ctx, req)
if err != nil {
return nil, err
}
return skyl.CollectStream(s, c.Provider().Name(), req.Model)
}Troubleshooting#
resp.Raw is nil
Expected. There is no single provider body behind an assembled response. If you
need Raw and streaming both, accumulate StreamEvent.Raw yourself — noting
that its coverage varies by adapter.
Nothing printed until the end
CollectStream returns only when the stream is finished. For live output, write
the event loop.
Why must I pass provider and model?
A Stream does not carry them — the events are deltas with no envelope. Adding
accessors to the interface would make every third-party adapter implement two
more methods to satisfy one convenience function, which is a bad trade for a
four-method seam.