@kestrel-agents/ai-sdk
v0.6.0
Published
Typed Vercel AI SDK presentation adapter for Kestrel runner streams
Downloads
56
Readme
@kestrel-agents/ai-sdk
Typed presentation adapter between Kestrel runner events and Vercel AI SDK 6 UI messages.
The package turns one Kestrel run stream into a durable assistant message with human-facing text, typed activity parts, terminal metadata, and visible contract failures. The same accumulator drives the live UI stream and the message you persist, preventing them from developing different interpretations of a run.
When to Use It
Use this package in a server-side Vercel AI SDK application when you want to render Kestrel:
- runtime and committed agent progress
- live provider reasoning when the provider exposes it
- tool activity
- citations and artifacts
- approval or elicitation interactions
- completed, waiting, failed, cancelled, or contract-failure state
- terminal
assistantText
It is a presentation adapter, not a runtime, transport, React component library,
or browser client. Start and control the run through
@kestrel-agents/sdk.
Install
pnpm add @kestrel-agents/[email protected] \
@kestrel-agents/[email protected] \
ai@^6The runtime, protocol, SDK, and presentation adapter must use a compatible release line. Check 0.6 Beta release status before pinning a production dependency.
Stream a Kestrel Run
Inside a Vercel AI SDK createUIMessageStream execute callback, pass the
writer and the Kestrel SDK stream to the adapter:
import type { UIMessageStreamWriter } from "ai";
import type {
RunnerRunStreamEvent,
RunnerRunTerminalEvent,
RunnerStream,
} from "@kestrel-agents/sdk";
import {
type KestrelUIMessage,
writeKestrelFailureToUIMessage,
writeKestrelRunnerStreamToUIMessage,
} from "@kestrel-agents/ai-sdk";
async function writeRun(
writer: UIMessageStreamWriter<KestrelUIMessage>,
runStream: RunnerStream<RunnerRunStreamEvent, RunnerRunTerminalEvent>,
turnId: string,
) {
try {
return await writeKestrelRunnerStreamToUIMessage({
writer,
events: runStream,
terminalEvent: runStream.result,
assistantMessageId: crypto.randomUUID(),
textPartId: crypto.randomUUID(),
turnId,
});
} catch (error) {
return writeKestrelFailureToUIMessage({
writer,
error,
assistantMessageId: crypto.randomUUID(),
textPartId: crypto.randomUUID(),
turnId,
});
}
}The returned KestrelPresentationSnapshot contains:
message: the complete assistantUIMessageto persistmessage.metadata.kestrelTurnId: the host product's durable turn identity, when suppliedassistantText: canonical human-facing terminal text ornullterminalStatus:working,completed,waiting,failed,cancelled, orcontract_failureerrorMessage: visible normalized failure detail when presentfailureVisible: whether the terminal failure must remain visible in the UIinteraction: the durable pending interaction when the run is waiting
Use one pair of message/text part IDs for one assistant message. If the caller reconnects to the same durable run, preserve the Kestrel event cursor and your application's message identity rules.
assistantText is producer-owned. Completed runs require a non-empty response;
user-facing waits require assistantText to equal the interaction prompt. A
data-kestrel-status part describes that run segment, while the host product's
durable turn ledger remains authoritative for the current multi-segment turn
state.
Typed Message Parts
KestrelPresentationDataParts adds these AI SDK data parts:
| Part | Meaning | Persisted |
| --- | --- | --- |
| data-kestrel-progress | Runtime, environment, or worker progress | Yes |
| data-kestrel-agent-progress | Committed agent progress | Yes |
| data-kestrel-provider-reasoning | Provider-exposed live reasoning | No; emitted as transient |
| data-kestrel-tool | Tool start, completion, or failure | Yes |
| data-kestrel-citation | Citation metadata | Yes |
| data-kestrel-artifact | Produced artifact metadata | Yes |
| data-kestrel-interaction | Pending approval, input, MCP sampling, or elicitation | Yes |
| data-kestrel-status | Terminal or contract-failure state | Yes |
Provider reasoning is deliberately live-only. Committed agent progress is a durable product event and remains in the persisted message.
Use the Accumulator Directly
Use createKestrelPresentationAccumulator() when your server framework owns
its own stream transport but still needs the canonical presentation contract:
import { createKestrelPresentationAccumulator } from "@kestrel-agents/ai-sdk";
const presentation = createKestrelPresentationAccumulator({
assistantMessageId: "assistant-123",
turnId: "turn-123",
});
for await (const event of runStream) {
const newParts = presentation.append(event);
// Send newParts through your transport.
}
const snapshot = presentation.finish(await runStream.result);
// Persist snapshot.message.The accumulator deduplicates identified parts and validates terminal waiting
interactions. Invalid or contradictory presentation data becomes a visible
KestrelPresentationContractError state instead of disappearing from the UI.
Terminal Interactions
readKestrelTerminalInteraction() extracts a durable pending interaction from
a waiting terminal event. It verifies that the canonical assistantText
matches the interaction prompt so the user is not shown two contradictory
requests.
Exports
writeKestrelRunnerStreamToUIMessagewriteKestrelFailureToUIMessagecreateKestrelPresentationAccumulatorreadKestrelTerminalInteractionKestrelPresentationContractError- typed message, metadata, part, snapshot, status, tool, citation, artifact, progress, reasoning, and interaction contracts
Requirements
- Node.js 20 or newer
- Vercel AI SDK
>=6 <7 - compatible
@kestrel-agents/sdkand protocol contracts - server-side execution; do not expose runner credentials to the browser
Development
pnpm run ai-sdk:test
pnpm run ai-sdk:build
pnpm run ai-sdk:release-check