@meistrari/agent-sdk
v0.10.1
Published
A small, publishable client for the **agent-api** sandbox execution path. It owns the HTTP contract so consumers (tela, chat-app, tela-dock) don't re-implement the execute request, the SSE session parser, and `vault://` reference resolution by hand.
Maintainers
Keywords
Readme
@meistrari/agent-sdk
A small, publishable client for the agent-api sandbox execution path. It owns the
HTTP contract so consumers (tela, chat-app, tela-dock) don't re-implement the execute
request, the SSE session parser, and vault:// reference resolution by hand.
Install
bun add @meistrari/agent-sdkUsage
Prepare a session with its runtime environment
Environment variables required by the first turn must be supplied to wakeAgent() so
the parked worker is primed with them. Credential-bearing wakes are reserved for the
returned session id; claim that exact session without resending the values.
const wakeInput = {
organizationName: 'acme',
repository: 'support-bot',
ref: 'published-commit',
environmentVariables: { SUPPORT_API_KEY: supportApiKey },
}
let wake = await client.wakeAgent(wakeInput)
while (wake.success && wake.status !== 'ready') {
await new Promise(resolve => setTimeout(resolve, 500))
wake = await client.wakeAgent(wakeInput)
}
if (!wake.success)
throw new Error(wake.error)
const execution = await client.executeAgentLive({
organizationName: 'acme',
repository: 'support-bot',
ref: 'published-commit',
wakeSessionId: wake.sessionId,
message: 'Summarize the latest tickets',
})Start and stream a session
import { agentClient, DataTokenAuthStrategy } from '@meistrari/agent-sdk'
const client = agentClient({
baseUrl: 'https://agent-api.example.com',
authStrategy: new DataTokenAuthStrategy(dataToken),
vaultUrl: 'https://vault.example.com', // required only for resolveReference
})
const { success, sessionId, error } = await client.executeAgent({
organizationName: 'acme',
repository: 'support-bot',
message: 'Summarize the latest tickets',
inputs: [{ vaultRef: 'vault://...', filename: 'tickets.csv' }],
})
if (!success || !sessionId)
throw new Error(error ?? 'Agent execution was not accepted')
let cursor: number | undefined
let readyForFollowUp = false
// The server already inlines step/result Vault bytes into the stream.
for await (const event of await client.streamSession(sessionId, { signal })) {
if ('nextCursor' in event && event.nextCursor !== null)
cursor = event.nextCursor
switch (event.kind) {
case 'status': // pending | running | completed | failed | waiting_messages | cancelled
break
case 'steps': // live step tail
break
case 'result': // final accumulated result
readyForFollowUp = event.status === 'waiting_messages'
break
case 'error':
throw new Error(event.error)
}
}Stream live model deltas
Use streamSessionLive() when the UI needs best-effort provider deltas before the
authoritative durable step arrives. It returns SessionLiveStreamEvent, which adds
direct-stream and message-parts to the stable SessionStreamEvent variants.
Existing streamSession() consumers keep the durable event union unchanged.
const liveExecution = await client.executeAgentLive({
organizationName: 'acme',
repository: 'support-bot',
message: 'Summarize the latest tickets',
})
if (!liveExecution.success)
throw new Error(liveExecution.error)
const { sessionId: liveSessionId } = liveExecution
for await (const event of await client.streamSessionLive(liveSessionId, { signal })) {
if (event.kind === 'message-parts') {
// Render the raw provider delta optimistically.
}
else if (event.kind === 'steps') {
// Reconcile with the authoritative durable step.
}
}executeAgentLive() sends x-v4-direct-stream-enabled: true on that execute request.
The standard executeAgent() method does not opt into direct streaming.
Continue a multi-turn session
For v4 sandbox sessions, executeAgent is both the start and continue method. Call it
with organizationName, repository, and message to start a session. Call it with
sessionId and a new message to continue an existing multi-turn session.
if (!readyForFollowUp)
throw new Error('Session is not waiting for follow-up messages')
const followUp = await client.executeAgent({
sessionId,
message: 'Which tickets should we prioritize first?',
inputs: [{ vaultRef: 'vault://...', filename: 'sla-rules.md' }],
environmentVariables: { SUPPORT_REGION: 'us' },
})
if (!followUp.success)
throw new Error(followUp.error ?? 'Agent continuation was not accepted')
for await (const event of await client.streamSession(sessionId, { cursor, signal })) {
if ('nextCursor' in event && event.nextCursor !== null)
cursor = event.nextCursor
// Handle only events newer than the cursor from the previous turn.
}Multi-turn lifecycle rules:
- New sessions require
organizationName,repository, andmessage. - Continuations require
sessionIdandmessage;inputsandenvironmentVariablesare optional per turn. - Recovery requests require
sessionIdandrecover: true;messageis not required. - Continue only after the stream reaches a
resultevent withstatus: 'waiting_messages'. - Continuations while the session is
pendingorrunningfail with409. - Treat
completed,failed, andcancelledas non-normal follow-up states in SDK consumers. - Continue by calling
executeAgent()again withsessionId; consume the updated run throughstreamSession().
Cancel a running session
const cancellation = await client.cancelSession(sessionId)
if (cancellation.success)
console.log(cancellation.message) // 'Message cancelled'
else
console.error(cancellation.error)cancelSession aborts the active turn (the model stops generating, ending token spend)
and restores the session to a continuable state: waiting_messages for multi-turn
sessions, cancelled for single-shot runs. A session that is not currently running
rejects with a FetchError (HTTP 409); branch on it with isFetchError.
Register session webhooks
Pass webhooks to executeAgent to receive lifecycle callbacks instead of polling.
Each entry takes an HTTPS url, an optional events filter, and an optional HMAC
secret:
import type { SessionWebhookEventPayload } from '@meistrari/agent-sdk'
const { success, sessionId } = await client.executeAgent({
organizationName: 'acme',
repository: 'support-bot',
message: 'Summarize the latest tickets',
webhooks: [{
url: 'https://example.com/hooks/agent-session',
events: ['session.completed', 'session.failed', 'session.cancelled'],
secret: 'whsec_a-long-random-string',
}],
})Webhook semantics (full contract in docs/webhooks.md):
- Available on the v4 execute path only; up to 5 webhooks per run.
- URLs must be HTTPS; localhost,
.local/.internal, and private/loopback IP hosts are rejected. eventsdefaults to allsession.*events (started,completed,failed,cancelled,waiting_messages);subagent.started/subagent.completedmust be opted into.- Webhooks are runtime-only and per-run. A continuation must pass
webhooksagain to receive callbacks; omitting it or passingwebhooks: []means no callbacks for that continuation run. - Deliveries are at-least-once with retries — make handlers idempotent.
- The payload (
SessionWebhookEventPayload) includeseventType,sessionId,runId,status,error,usage,subagent,deliveryId, andresultonsession.completed.resultis the postprocessed output: structured JSON when present, otherwise text, otherwisenull. Fetch the full stream withstreamSessionand timeline summary withfetchTimeline.
When a secret is configured, verify the x-tela-agent-webhook-signature header with the bundled
helper (Web Crypto, works in Node/Bun/edge runtimes):
import { verifyWebhookSignature } from '@meistrari/agent-sdk'
app.post('/hooks/agent-session', async (req) => {
const rawBody = await req.text()
const valid = await verifyWebhookSignature(
rawBody,
req.headers.get('x-tela-agent-webhook-signature'),
process.env.WEBHOOK_SECRET!,
)
if (!valid)
return new Response('invalid signature', { status: 401 })
const event = JSON.parse(rawBody) as SessionWebhookEventPayload
// ...
return new Response('ok')
})For Trigger.dev wait-for-token flows, pass the
token's callback URL as a webhook filtered to terminal events — no secret needed (auth
is embedded in the token URL), and pass a fresh token URL on each continuation turn.
Update an agent model
const update = await client.updateAgentModel({
repository: 'support-bot',
model: 'claude-sonnet-4-5',
commitMessage: 'Switch support bot model',
})
if (update.success) {
console.log(update.commitHash)
}
else {
console.error(update.error)
}updateAgentModel writes agent.config.json on the main branch and lets agent-api
synchronize provider-specific template files when the model provider changes.
Fetch a timeline summary
const timeline = await client.fetchTimeline(sessionId, { signal })
console.log(timeline.status, timeline.metrics, timeline.spans)fetchTimeline returns the persisted v4 timeline summary only: status, metrics,
prompt, and spans. It does not return per-event detail. Consumers that need timeline
events should consume streamSession and merge stream-derived timeline events locally.
Fetch captured session files
const files = await client.listSessionFiles(sessionId, { signal })
for (const file of files.tree) {
console.log(file.path, file.size)
}
const report = await client.fetchSessionFile(sessionId, 'output/report.md', { signal })
if (report.encoding === 'raw')
console.log(report.content)listSessionFiles returns the file tree captured from the source sandbox after execution
(GET /v4/sessions/{sessionId}/files). fetchSessionFile returns a single captured file
(GET /v4/sessions/{sessionId}/files/{filePath}). Text-like files are returned as
encoding: 'raw'; binary files are returned as base64.
Nested paths are encoded automatically by the SDK: pass the path as-is (e.g.
'input/receita/arquivo.pdf') and the client URL-encodes the slashes as %2F into a
single path segment, as the v4 router requires.
Fetch artifacts
Artifact payloads are exposed as vault:// references in streamSession() events.
Read the session stream, pick the reference you need from a step or result event, then
call resolveReference() to download the bytes, stream, or JSON payload.
Resolve Vault references
// Resolve a vault:// reference, e.g. an output file ref found in a result.
const bytes = await client.resolveReference('vault://...')
const stream = await client.resolveReference('vault://...', { as: 'stream' })
const json = await client.resolveReference<MyShape>('vault://...', { as: 'json' })Auth
DataTokenAuthStrategy sends x-data-token — accepted by both agent-api and Vault, so a
single strategy covers executeAgent/streamSession and resolveReference.
APIKeyAuthStrategy sends Authorization: Bearer <apiKey> for agent-api-only use.
License
This package mirrors @meistrari/vault-sdk and is published as UNLICENSED.
Scope
Focused on sandbox execution and the public v4 consumer paths: executeAgent (including
session webhook registration), streamSession, cancelSession, updateAgentModel,
fetchTimeline, listSessionFiles, fetchSessionFile, resolveReference, and webhook
signature verification via verifyWebhookSignature. Agent CRUD, legacy polling, and the
internal sandbox timeline callback are intentionally out of scope.
