PAIPAI

AgentClient

Typed thread facade: send, watch, files, commands.

AgentClient is what createPaiHttpClient().agent(id) returns. React wraps it. Custom transports use createAgentClient(transport) from @pai/client.

const assistant = paiClient.agent("main");
const thread = assistant.thread(assistant.newThreadId());
const run = await thread.send("Say hello.");
const state = await run.waitUntilIdle();

newThreadId() is just an id. The first accepted send creates the stored thread.

Import

import { createPaiHttpClient } from "@pai/client-http";
import { createAgentClient } from "@pai/client";

Signature

function createAgentClient(
  transport: AgentClientTransport,
  options?: AgentClientOptions,
): AgentClient;

For a custom transport, AgentClientTransport.send() returns a ThreadSendResult with runId, version, run, and the accepted message. The returned run contains the lifecycle status; the receipt has no separate status field.

AgentClientTransport.watchThread(input) is required. It returns an AsyncIterable of transport events for the requested thread. When input.includeInitial is true, yield a snapshot before incremental events; the client uses it to establish the current state and to recover after a disconnection. Read that snapshot only after the live subscription is active, so no change can fall between the snapshot and the first incremental event. Honor input.signal when the watch is disposed. getThreadState is used only for finite reads, such as refresh(), threads.find(), and recovery after a watch event the client cannot apply; it is never polled. watchThreadHeads is optional and controls whether threads.watchHeads() is available.

During the queue-removal rollout, the HTTP receiver still serializes status: "started" for older browser bundles. This temporary network field is not part of the current transport API; see deployment compatibility.

AgentClient

type AgentClient<TContract> = {
  getManifest(): Promise<AgentManifest<TContract>>;
  newThreadId(): ThreadId;
  thread(threadId: ThreadId, options?: ThreadHandleOptions): Thread<TContract>;
  threads: ThreadCollectionClient<TContract>;
  files: {
    upload(input: FileUploadInput): Promise<UploadedFile>;
    url(input: ClientCreateFileUrlInput): Promise<{ url: string; expiresAt?: Date } | null>;
    read(input: ClientReadFileInput): Promise<StoredFileBody>;
  };
  close?(): Promise<void>;
};

Usual call sites. Also on the client: getManifest, files.read, close, and more threads methods (rename, delete, find).

newThreadId: () => ThreadId

thread: (threadId, options?) => Thread

Handle for one id. Does not fetch until refresh / send / watch.

threads.list: (options?) => Promise<ThreadListResult>

files.upload: (input) => Promise<UploadedFile>

files.url: (input) => Promise<{ url: string; expiresAt?: Date } | null>

Thread

type Thread<TContract> = {
  readonly threadId: ThreadId;
  readonly commands: ThreadCommands<TContract>;
  getState(): ThreadState<TContract>;
  refresh(): Promise<ThreadState<TContract>>;
  send(input, options?): Promise<RunHandle<TContract>>;
  stop(input?): Promise<ActionResult<TContract>>;
  regenerate(input, options?): Promise<RunHandle<TContract>>;
  retry(options?): Promise<RunHandle<TContract>>;
  rename(title: string): Promise<ThreadState<TContract>>;
  watch(options?: ThreadWatchOptions): AsyncIterable<ThreadState<TContract>>;
  run(runId: RunId): RunHandle<TContract>;
};

getState: () => ThreadState

Latest snapshot. Not .state.

send: (input, options?) => Promise<RunHandle>

String or { text, attachments }. A second send while the thread is busy throws ThreadBusyError.

clientData?: TContract['clientData']

Untrusted per-send extras. See Identity.

messageId?: string

commands: ThreadCommands

Exposed defineCommand map.

stop: (input?) => Promise<ActionResult>

regenerate: (input, options?) => Promise<RunHandle>

input.messageId is required.

retry: (options?) => Promise<RunHandle>

rename: (title: string) => Promise<ThreadState>

watch: (options?: ThreadWatchOptions) => AsyncIterable<ThreadState>

Stream thread state updates. Pass signal to stop observation. Every watch on one handle, including its run waiters, shares one live subscription: a watch that joins starts from the handle's current state, and the subscription closes when the last watch stops. The shared subscription retries with the reconnect tuning of the watch that opened it, and every watch receives its status changes and reconnect callbacks. Each watch receives every state in order and buffers what it has not read yet, so abort or return a watch you stop reading.

run: (runId) => RunHandle

refresh: () => Promise<ThreadState>

RunHandle.waitUntilIdle() waits until the run completes, fails, or is cancelled; waitUntilBlocked() also returns when it is waiting on a person. Both resolve from the thread's live watch.

Learn more

On this page