Skip to main content
persona.chat — /api/v1/developer/agui. Runs an Agent as the asserted external user, streaming the response as AG-UI events. Requires the client to have been constructed with externalUserId set — a bare Project credential has no Subject to chat as; the server rejects it with 400 EXTERNAL_USER_REQUIRED.

Methods

stream(agentId, options)

Yields each raw AG-UI event as it arrives:
  • agentId — the Agent to run.
  • The underlying request is POST /api/v1/developer/agui with x-agent-id: <agentId> and (when set) x-thread-id: <threadId> headers — routing is header-based, not body-based.
  • Malformed/partial SSE frames are skipped rather than thrown; the stream ends when the run finishes.

sendMessage(agentId, options)

Convenience wrapper over stream() — drains the full run and returns:

SendMessageOptions

threadId must belong to the asserted external user and the agentId you’re calling with — passing someone else’s Thread, a different Agent’s Thread, or a nonexistent id throws a PersonaApiError with statusCode: 404. It does not silently fall back to a different conversation.

AG-UI event types

stream() yields the standard @ag-ui/core EventType union. The SDK re-exports EventType from the package root so you don’t need a separate @ag-ui/core dependency just to compare event.type: The full, versioned JSON Schema for every CUSTOM event’s payload is served at GET /api/v1/developer/agui/schema (not yet wrapped by a typed method — call it via HttpClient if you need it). Every stream response carries the active schema version on the X-AGUI-Schema-Version header. The TypeScript counterparts (ClarificationRequestPayload, HitlRequestPayload, McpAppPayload, SubagentActivityPayload) are exported from the package root and kept in sync with that document.

Human-in-the-loop: interrupts and resuming

If an Agent’s interruptOn config requires confirming a sensitive tool call, or the run needs a clarifying answer, the run pauses instead of finishing normally: it emits a CUSTOM event instead of RUN_FINISHED, and sendMessage() detects this and sets result.interrupt:
Resume on the next sendMessage()/stream() call for the same Thread, with messages: [] (no new user message — you’re answering the interrupt, not starting a new turn) and resume set:
ChatResume is a union: { decisions: unknown[] } (for hitl) | { answers: unknown[]; text?: string } (for clarification) | Record<string, unknown> (for anything else). The shape you must pass depends on the interrupt kind. ChatInterrupt.value is typed unknown — its concrete shape depends on the interrupt payload (see the HitlRequestPayload/ClarificationRequestPayload types for the backend’s shapes).

Structured run errors

If a run ends in a genuine failure — not an interrupt — sendMessage() sets result.error instead of result.interrupt, as a typed PersonaRunErrorEvent:
PersonaRunErrorEvent: { type: 'RUN_ERROR', code: RunErrorCode, message: string, retryable: boolean, providerName?: string }. RunErrorCode values:
This backend proxies arbitrary OpenAI-compatible baseURLs per Provider, so classification is best-effort and improves over time as real provider error shapes are observed — retryable should still be trusted (it’s conservative), but don’t assume every failure mode is perfectly distinguished yet.

Cancelling a chat call

Complete example — a minimal chat endpoint