Superserve agents can stream responses in real-time using Server-Sent Events (SSE). The SDK provides the AgentStream class to consume these events as async iterables.
Basic Streaming
Use client.stream() or session.stream() to get an AgentStream:
import Superserve from '@superserve/sdk'
const client = new Superserve ({ apiKey: 'ss_...' })
const stream = client . stream ( 'my-agent' , {
message: 'Write a report about TypeScript'
})
for await ( const chunk of stream . textStream ) {
process . stdout . write ( chunk )
}
console . log ( ' \n Done!' )
AgentStream API
The AgentStream class provides multiple ways to consume events:
textStream
An async iterable that yields only text chunks:
const stream = client . stream ( 'my-agent' , {
message: 'Hello'
})
for await ( const text of stream . textStream ) {
process . stdout . write ( text )
}
Iterating All Events
Iterate over all event types:
for await ( const event of stream ) {
switch ( event . type ) {
case 'text' :
process . stdout . write ( event . content )
break
case 'tool-start' :
console . log ( ` \n [Using ${ event . name } ]` )
break
case 'tool-end' :
console . log ( `[Completed in ${ event . duration } ms]` )
break
case 'run-completed' :
console . log ( ' \n Run completed!' )
break
case 'run-failed' :
console . error ( 'Run failed:' , event . error )
break
}
}
result
A promise that resolves with the final RunResult once the stream completes:
const stream = client . stream ( 'my-agent' , {
message: 'Analyze this code'
})
// Consume the stream
for await ( const chunk of stream . textStream ) {
process . stdout . write ( chunk )
}
// Get the final result
const result = await stream . result
console . log ( ' \n Total duration:' , result . duration , 'ms' )
console . log ( 'Tools used:' , result . toolCalls . length )
If you access result without iterating the stream, it will automatically consume the stream in the background:
const stream = client . stream ( 'my-agent' , { message: 'Hello' })
// Auto-consumes the stream
const result = await stream . result
console . log ( result . text )
abort()
Cancel a stream in progress:
const stream = client . stream ( 'my-agent' , {
message: 'Write a very long document'
})
setTimeout (() => {
stream . abort ()
console . log ( ' \n Stream aborted!' )
}, 5000 )
for await ( const chunk of stream . textStream ) {
process . stdout . write ( chunk )
}
Event Types
The SDK emits the following event types:
TextEvent
interface TextEvent {
type : 'text'
content : string
}
Emitted for each chunk of text from the agent.
interface ToolStartEvent {
type : 'tool-start'
name : string
input : unknown
}
Emitted when the agent starts using a tool.
interface ToolEndEvent {
type : 'tool-end'
duration : number
}
Emitted when a tool invocation completes. duration is in milliseconds.
RunCompletedEvent
interface RunCompletedEvent {
type : 'run-completed'
duration : number
maxTurnsReached : boolean
}
Emitted when the agent finishes successfully.
RunFailedEvent
interface RunFailedEvent {
type : 'run-failed'
error : string
}
Emitted when the run fails.
Callbacks
You can provide callbacks instead of manually iterating:
const stream = client . stream ( 'my-agent' , {
message: 'Write a poem' ,
onText : ( text ) => {
process . stdout . write ( text )
},
onToolStart : ( event ) => {
console . log ( ` \n [Tool: ${ event . name } ]` )
console . log ( 'Input:' , JSON . stringify ( event . input , null , 2 ))
},
onToolEnd : ( event ) => {
console . log ( `[Completed in ${ event . duration } ms]` )
},
onFinish : ( result ) => {
console . log ( ' \n\n Final result:' )
console . log ( 'Text length:' , result . text . length )
console . log ( 'Tools used:' , result . toolCalls . length )
console . log ( 'Duration:' , result . duration , 'ms' )
},
onError : ( error ) => {
console . error ( 'Stream error:' , error )
}
})
// Wait for completion
await stream . result
Callback Options
Called for each text chunk
onToolStart
(event: ToolStartEvent) => void
Called when a tool starts executing
onToolEnd
(event: ToolEndEvent) => void
Called when a tool completes
onFinish
(result: RunResult) => void
Called when the run completes successfully
Examples
Console Progress Indicator
let dotCount = 0
const stream = client . stream ( 'my-agent' , {
message: 'Analyze this codebase' ,
onText : ( text ) => {
process . stdout . write ( text )
},
onToolStart : ( event ) => {
console . log ( ` \n ⏳ Using ${ event . name } ...` )
const interval = setInterval (() => {
process . stdout . write ( '.' )
dotCount ++
}, 500 )
// Store interval to clear later
event . input . __interval = interval
},
onToolEnd : ( event ) => {
clearInterval ( event . input ?. __interval )
console . log ( `✓ Done ( ${ event . duration } ms)` )
}
})
await stream . result
Building a Progress Bar
import { SingleBar } from 'cli-progress'
const bar = new SingleBar ({})
bar . start ( 100 , 0 )
let progress = 0
const stream = client . stream ( 'my-agent' , {
message: 'Generate a report' ,
onText : () => {
progress += 1
bar . update ( Math . min ( progress , 90 ))
},
onFinish : () => {
bar . update ( 100 )
bar . stop ()
console . log ( 'Complete!' )
}
})
await stream . result
Web Server Integration
Stream agent responses to HTTP clients:
import express from 'express'
import Superserve from '@superserve/sdk'
const app = express ()
const client = new Superserve ({ apiKey: 'ss_...' })
app . get ( '/chat' , async ( req , res ) => {
res . setHeader ( 'Content-Type' , 'text/event-stream' )
res . setHeader ( 'Cache-Control' , 'no-cache' )
res . setHeader ( 'Connection' , 'keep-alive' )
const stream = client . stream ( 'my-agent' , {
message: req . query . message as string ,
onText : ( text ) => {
res . write ( `data: ${ JSON . stringify ({ type: 'text' , content: text }) } \n\n ` )
},
onToolStart : ( event ) => {
res . write ( `data: ${ JSON . stringify ( event ) } \n\n ` )
},
onFinish : ( result ) => {
res . write ( `data: ${ JSON . stringify ({ type: 'done' , result }) } \n\n ` )
res . end ()
},
onError : ( error ) => {
res . write ( `data: ${ JSON . stringify ({ type: 'error' , error: error . message }) } \n\n ` )
res . end ()
}
})
// Cleanup on client disconnect
req . on ( 'close' , () => {
stream . abort ()
})
})
app . listen ( 3000 )
Collecting All Events
const events : StreamEvent [] = []
for await ( const event of stream ) {
events . push ( event )
if ( event . type === 'text' ) {
process . stdout . write ( event . content )
}
}
console . log ( ' \n Total events received:' , events . length )
console . log ( 'Text events:' , events . filter ( e => e . type === 'text' ). length )
console . log ( 'Tool events:' , events . filter ( e => e . type === 'tool-start' ). length )
Streaming vs. Promise-Based
Choose the right pattern for your use case:
Use streaming when you want:
Real-time feedback in the UI
Progress indicators
Lower perceived latency
Ability to cancel mid-execution
const stream = client . stream ( 'my-agent' , {
message: 'Write a long document' ,
onText : ( text ) => updateUI ( text )
})
for await ( const chunk of stream . textStream ) {
process . stdout . write ( chunk )
}
Use promise-based when you:
Only need the final result
Don’t need real-time updates
Want simpler code
const result = await client . run ( 'my-agent' , {
message: 'Analyze this code'
})
console . log ( result . text )
Error Handling
Handle errors during streaming:
import { APIError } from '@superserve/sdk'
try {
const stream = client . stream ( 'my-agent' , {
message: 'Hello'
})
for await ( const chunk of stream . textStream ) {
process . stdout . write ( chunk )
}
const result = await stream . result
console . log ( ' \n Success!' )
} catch ( error ) {
if ( error instanceof APIError ) {
console . error ( 'API error:' , error . status , error . message )
} else {
console . error ( 'Unexpected error:' , error )
}
}
Or use the onError callback:
const stream = client . stream ( 'my-agent' , {
message: 'Hello' ,
onError : ( error ) => {
console . error ( 'Stream failed:' , error . message )
}
})
await stream . result
Best Practices
Wrap stream iteration in try/catch or use the onError callback: const stream = client . stream ( 'my-agent' , {
message: 'Hello' ,
onError : ( error ) => {
console . error ( 'Failed:' , error )
// Update UI, log to monitoring, etc.
}
})
An AgentStream can only be iterated once. Attempting to iterate multiple times will throw an error: const stream = client . stream ( 'my-agent' , { message: 'Hi' })
for await ( const chunk of stream . textStream ) {
// First iteration works
}
for await ( const chunk of stream . textStream ) {
// Throws: "AgentStream can only be iterated once"
}
If you need to process events multiple times, collect them into an array: const chunks : string [] = []
for await ( const chunk of stream . textStream ) {
chunks . push ( chunk )
}
// Now process chunks multiple times
console . log ( 'First word:' , chunks [ 0 ])
console . log ( 'Full text:' , chunks . join ( '' ))
When aborting a stream, handle cleanup properly: const stream = client . stream ( 'my-agent' , { message: 'Hi' })
setTimeout (() => {
stream . abort ()
}, 5000 )
try {
for await ( const chunk of stream . textStream ) {
process . stdout . write ( chunk )
}
} catch ( error ) {
if ( error . message . includes ( 'abort' )) {
console . log ( ' \n Stream was aborted' )
}
}
Use callbacks for side effects
Use callbacks for UI updates, logging, and other side effects: const stream = client . stream ( 'my-agent' , {
message: 'Hello' ,
onText : ( text ) => updateUI ( text ),
onToolStart : ( event ) => logToolUsage ( event ),
onFinish : ( result ) => saveToDatabase ( result )
})
await stream . result
Next Steps
Sessions Build multi-turn streaming conversations
React Hooks Use streaming in React applications