@meistrari/agent-sdk
v0.5.0
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
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)
}
}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. - The SDK v4 path does not use the legacy
/v3/sessions/{sessionId}/continueendpoint.
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 session.
- 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.- Passing
webhookson a continuation replaces the registered set; omitting it keeps the existing registration. - Deliveries are at-least-once with retries — make handlers idempotent.
- The payload (
SessionWebhookEventPayload) is lightweight metadata:eventType,sessionId,runId,status,error,usage,subagent,deliveryId. Fetch content withstreamSessionorfetchTimeline.
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 the waiting task resumes with the payload as the token
output.
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.
fetchSessionFile returns a single captured file. Text-like files are returned as
encoding: 'raw'; binary files are returned as base64.
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.
