-
Notifications
You must be signed in to change notification settings - Fork 303
Enhance SSE handling with raw text fallback and tests #7890
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -689,11 +689,14 @@ func handleInvocationSync(body io.Reader, agentName string) error { | |||||||
|
|
||||||||
| // handleInvocationSSE handles a streaming (200 OK, text/event-stream) invocations response. | ||||||||
| // The invocations protocol has a developer-defined SSE format, so we print data lines as they arrive. | ||||||||
| // As a fallback, lines that are not SSE control lines (event:, id:, : comment, or blank) are | ||||||||
| // printed as raw text output — this supports agents that stream raw text over text/event-stream. | ||||||||
| func handleInvocationSSE(w io.Writer, body io.Reader, agentName string) error { | ||||||||
| scanner := bufio.NewScanner(body) | ||||||||
| scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) | ||||||||
|
|
||||||||
| var printed bool | ||||||||
| var lastWasRaw bool | ||||||||
|
|
||||||||
| for scanner.Scan() { | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] SSE spec non-compliance: Both SSE parsers use Suggested fix: Parse
Suggested change
Apply to both |
||||||||
| line := scanner.Text() | ||||||||
|
|
@@ -728,13 +731,35 @@ func handleInvocationSSE(w io.Writer, body io.Reader, agentName string) error { | |||||||
| printed = true | ||||||||
| } | ||||||||
| fmt.Fprintln(w, data) | ||||||||
| lastWasRaw = false | ||||||||
| continue | ||||||||
| } | ||||||||
|
|
||||||||
| // Skip SSE control lines and blank lines | ||||||||
| if line == "" || | ||||||||
| strings.HasPrefix(line, "event:") || | ||||||||
| strings.HasPrefix(line, "id:") || | ||||||||
|
||||||||
| strings.HasPrefix(line, "id:") || | |
| strings.HasPrefix(line, "id:") || | |
| strings.HasPrefix(line, "retry:") || |
Copilot
AI
Apr 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blank lines are always skipped. For the raw-text fallback, this means consecutive newlines (e.g., paragraph breaks) in a raw text stream will be lost, changing the content/formatting. Consider preserving blank lines once you’ve entered raw mode (e.g., if lastWasRaw is true, emit a newline instead of skipping).
Copilot
AI
Apr 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the new raw-text fallback, valid SSE lines like data:foo (no space after the colon) will fall through and be printed as raw text because earlier parsing only matches "data: " with a required space. SSE allows an optional single space after data:, so consider handling data: with/without the space and trimming at most one leading space from the payload (consistent with monitor_format.go’s SSE parsing).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This concatenates consecutive raw text lines with no separator. Matches the Agents API handler at L983 which does the same for streaming tokens. The test "multiple raw text lines are concatenated" works because the first line has a trailing space in the input ("Hello "). If the intent is token-by-token streaming, a brief comment here would help clarify. If full lines are expected, fmt.Fprintln would match the data: path behavior.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -468,6 +468,30 @@ func TestHandleInvocationSSE(t *testing.T) { | |||||||||||||||||||||||||||||
| agentName: "agent", | ||||||||||||||||||||||||||||||
| wantOutput: "[agent] line1\nline2\nline3\n", | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| name: "raw text without data prefix is printed as fallback", | ||||||||||||||||||||||||||||||
| input: "Hello from agent\n", | ||||||||||||||||||||||||||||||
| agentName: "raw-agent", | ||||||||||||||||||||||||||||||
| wantOutput: "[raw-agent] Hello from agent\n", | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| name: "multiple raw text lines are concatenated", | ||||||||||||||||||||||||||||||
| input: "Hello \nworld!\n", | ||||||||||||||||||||||||||||||
| agentName: "raw-agent", | ||||||||||||||||||||||||||||||
| wantOutput: "[raw-agent] Hello world!\n", | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| name: "mixed SSE data and raw text", | ||||||||||||||||||||||||||||||
| input: "data: sse-line\nraw-text\n\n", | ||||||||||||||||||||||||||||||
| agentName: "mix-agent", | ||||||||||||||||||||||||||||||
| wantOutput: "[mix-agent] sse-line\nraw-text\n", | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| name: "SSE comment lines are skipped", | ||||||||||||||||||||||||||||||
| input: ": this is a comment\ndata: content\n\n", | ||||||||||||||||||||||||||||||
| agentName: "test-agent", | ||||||||||||||||||||||||||||||
| wantOutput: "[test-agent] content\n", | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| }, | |
| }, | |
| { | |
| name: "SSE data lines without space after colon are handled", | |
| input: "data:content\n\n", | |
| agentName: "test-agent", | |
| wantOutput: "[test-agent] content\n", | |
| }, | |
| { | |
| name: "SSE retry lines are ignored", | |
| input: "retry: 1000\ndata: content\n\n", | |
| agentName: "test-agent", | |
| wantOutput: "[test-agent] content\n", | |
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Medium] Missing test coverage for raw→data transition and data: without space
The test suite doesn't cover:
- Raw text followed by a
data:line (the concatenation bug wbreza identified) data:valuewithout a space after the colon (the spec compliance issue)
Suggested additions:
{
name: "raw text followed by SSE data is separated by newline",
input: "raw-text\ndata: sse-line\n",
agentName: "test-agent",
wantOutput: "raw-text\n[test-agent] sse-line\n",
},
{
name: "SSE data without space after colon is parsed correctly",
input: "data:hello\n",
agentName: "test-agent",
wantOutput: "[test-agent] hello\n",
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Medium] Corrupted UTF-8 character in comment
This comment contains UTF-8 mojibake for an en-dash. Replace with an ASCII hyphen or proper en-dash.