Skip to content

Streaming

Every token, tool call, and step is streamed as typed events over SSE. swarmlord gives you two APIs for consuming them: callbacks for simple integrations, and an async iterator for full control.

Callback API

Pass callbacks to session.send() to react to events as they arrive:

typescript
const result = await session.send("Write a haiku about distributed systems", {
    onConnect: () => console.log("Connected"),
    onText: delta => process.stdout.write(delta),
    onReasoning: delta => process.stderr.write(delta),
    onToolStart: part => console.log(`[tool] ${part.tool} started`),
    onToolComplete: part => console.log(`[tool] ${part.tool} done`),
    onToolError: (part, error) => console.error(`[tool] ${part.tool}: ${error}`),
    onStepFinish: (tokens, cost) => console.log(`[step] ${tokens.input}in/${tokens.output}out — $${cost.toFixed(4)}`),
    onAttachment: file => console.log(`[file] ${file.filename} (${file.mime})`),
    onPermission: async req => "once",
    onStatus: status => console.log(`[status] ${status}`),
    onError: err => console.error(err),
});

// result contains the complete response after streaming finishes
console.log(result.text);
console.log(result.parts);

Callback Reference

CallbackFires whenArgument
onConnectSSE stream established
onTextNew text tokendelta: string
onReasoningReasoning/thinking tokendelta: string
onToolStartTool call beginspart: ToolPart
onToolCompleteTool call finishespart: ToolPart
onToolErrorTool call failspart: ToolPart, error: string
onStepFinishModel step completestokens: Tokens, cost: number
onAttachmentAgent produces a filefile: FilePart
onPermissionAgent needs permissionreq: PermissionRequest → return "once" | "always" | "reject"
onStatusSession status changesstatus: SessionStatus
onErrorStream errorerr: Error

Async Iterator API

For full control over every event, use session.stream():

typescript
for await (const event of session.stream("List the files in /workspace")) {
    switch (event.type) {
        case "connect":
            console.log("Connected");
            break;
        case "text-delta":
            process.stdout.write(event.delta);
            break;
        case "reasoning-delta":
            process.stderr.write(event.delta);
            break;
        case "tool-start":
            console.log(`> ${event.part.tool}(${JSON.stringify(event.part.state.input)})`);
            break;
        case "tool-complete":
            if (event.part.state.status === "completed") {
                console.log(`< ${event.part.state.title}`);
            }
            break;
        case "tool-error":
            console.error(`Tool error: ${event.error}`);
            break;
        case "step-finish":
            console.log(`[${event.tokens.input}in/${event.tokens.output}out]`);
            break;
        case "attachment":
            console.log(`File: ${event.file.filename}`);
            break;
        case "permission-asked":
            // Handle with session.replyToPermission()
            break;
        case "status-change":
            console.log(`Status: ${event.status}`);
            break;
        case "error":
            console.error(event.error);
            break;
    }
}

Event Types

EventFields
connect
text-deltadelta: string
reasoning-deltadelta: string
tool-startpart: ToolPart
tool-completepart: ToolPart
tool-errorpart: ToolPart, error: string
step-finishtokens: Tokens, cost: number, reason: string
attachmentfile: FilePart
permission-askedrequest: PermissionRequest
status-changestatus: SessionStatus
session-idlesessionId: string
errorerror: string
message-updatedinfo: MessageInfo

Handling Permissions

When the agent needs to do something that requires approval, you'll get a permission event. With callbacks:

typescript
await session.send("Install lodash and refactor utils.ts", {
    onText: delta => process.stdout.write(delta),
    onPermission: async req => {
        console.log(`Agent wants: ${req.permission}`);
        console.log(`Patterns: ${req.patterns.join(", ")}`);
        return "once"; // or "always" or "reject"
    },
});

With the iterator, use session.replyToPermission():

typescript
for await (const event of session.stream("Install lodash")) {
    if (event.type === "permission-asked") {
        await session.replyToPermission(event.request.id, "once");
    }
}

Which API Should I Use?

Use caseAPI
Simple CLI toolCallbacks — less boilerplate
Chat UI with custom renderingIterator — full event control
Auto-approve permissionsCallbacks with onPermission
Manual permission UIIterator with replyToPermission
Log everythingIterator — you see every event type

SDK released under the MIT License.