@kruntime/ag-ui
v0.1.15
Published
AG-UI protocol projection for KRuntime AgentSessions.
Readme
@kruntime/ag-ui
AG-UI protocol adapter for KRuntime AgentSessions.
K keeps its own durable event stream small and Unix-like. @kruntime/ag-ui
adapts that stream to the AG-UI protocol used by chat and agent frontends.
It is not a UI framework and it is not runtime truth.
import { AgUiEventType, agUiEvents } from '@kruntime/ag-ui'
const abort = new AbortController()
for await (const event of agUiEvents(agent, {
since: lastCursor,
signal: abort.signal,
})) {
socket.send(JSON.stringify(event))
}For HTTP/SSE frontends, keep the transport in this adapter layer:
import { agUiEvents, agUiSseResponse } from '@kruntime/ag-ui'
export function GET(request: Request) {
return agUiSseResponse(agUiEvents(agent, {
since: Number(request.headers.get('last-event-id') ?? -1),
}))
}Mental Model
AgentSession.events({ since })
durable K event cursor stream
|
v
@kruntime/ag-ui projector
AG-UI run/message/tool/custom events
|
v
web, H5, mini program, Electron, plugin, SaaS UIThe bridge does not become runtime truth. It only projects runtime truth:
turn { status: 'running' }->RUN_STARTEDcontent { kind: 'text' | 'reasoning' | ... }-> message content eventstool { status: 'requested' | ... }-> tool call eventsprocess { status: 'output' | ... }-> terminal/activity eventsapprovalandsignal-> custom state/activity events- terminal
turnstatuses ->RUN_FINISHEDorRUN_ERROR
Input messages are read from agent.messages.*, not from a public
input-specific event. K's public event stream describes runtime work; message
history describes conversation state.
AG-UI wants explicit run and message boundaries. K events already have turn boundaries, and the bridge can synthesize missing message starts and ends when a client reconnects in the middle of a turn. K core does not need AG-UI-specific start/end events for every UI concern.
UI Pattern
const agent = await computer.login('claw', { id: `web:${userId}` })
const abort = new AbortController()
const task = agent.run('请整理今天的记忆', { by: `web:${userId}` })
for await (const event of agUiEvents(agent, { since: cursor, signal: abort.signal })) {
renderAgUiEvent(event)
cursor = Number(event.k?.cursor ?? cursor)
if (event.type === AgUiEventType.RUN_FINISHED || event.type === AgUiEventType.RUN_ERROR) {
abort.abort()
}
}
await taskUse agent.on(...) for local in-process callbacks. Use
agent.events({ since, signal }) or agUiEvents(...) for UI, because cursor
streams survive reconnects and process recovery.
encodeAgUiSseEvent(...), agUiSseStream(...), and agUiSseResponse(...)
are Web-standard transport helpers. They do not add runtime state; they only
serialize projected AG-UI events as text/event-stream with the K cursor as
the SSE id.
