@agentick/client
Client SDK for multiplexed session access to a Agentick server. One client per app, many sessions per client.
Installation
pnpm add @agentick/client
npm install @agentick/client
Quick Start
import { createClient } from "@agentick/client";
const client = createClient({ baseUrl: "https://api.example.com" });
const conv = client.subscribe("conv-123");
conv.onEvent((event) => {
if (event.type === "content_delta") {
process.stdout.write(event.delta);
}
});
const handle = conv.send({
message: { role: "user", content: [{ type: "text", text: "Hello!" }] },
});
const result = await handle.result;
console.log(result.response);
API Reference
createClient(config)
Creates a new AgentickClient instance.
const client = createClient({
baseUrl: "https://api.example.com",
token: "bearer_token",
headers: { "x-tenant": "acme" },
paths: {
events: "/events",
send: "/send",
subscribe: "/subscribe",
abort: "/abort",
close: "/close",
toolResponse: "/tool-response",
channel: "/channel",
},
});
client.session(sessionId)
Returns a cold session accessor. No side effects until subscribe() or send() is called.
const conv = client.session("conv-123");
conv.subscribe();
client.subscribe(sessionId)
Returns a hot session accessor. Subscribes immediately (auto-connects if needed).
const conv = client.subscribe("conv-123");
client.send(input)
Ephemeral send. Creates a session, executes, then closes.
const handle = client.send({
message: { role: "user", content: [{ type: "text", text: "Quick question" }] },
props: { mode: "fast" },
});
for await (const event of handle) {
if (event.type === "content_delta") {
process.stdout.write(event.delta);
}
}
const result = await handle.result;
Session Accessor
accessor.subscribe() / accessor.unsubscribe()
Turns a cold accessor hot, or turns it cold again. Subscriptions are scoped to the session.
accessor.send(input)
Send to a session and return a ClientExecutionHandle.
accessor.onEvent(handler)
Receives events for this session only.
accessor.onResult(handler)
Receives final results for this session only (same as listening for type: "result").
accessor.onToolConfirmation(handler)
Receives tool confirmation requests for this session.
accessor.abort(reason?) / accessor.close()
Abort the current execution or close the session server-side.
accessor.invoke(method, params?)
Invoke a custom gateway method with auto-injected sessionId.
const session = client.subscribe("conv-123");
const tasks = await session.invoke("tasks:list");
const newTask = await session.invoke("tasks:create", {
title: "Buy groceries",
priority: "high",
});
await session.invoke("tasks:admin:archive");
accessor.stream(method, params?)
Invoke a streaming method with auto-injected sessionId. Returns an async generator.
const session = client.subscribe("conv-123");
for await (const change of session.stream("tasks:watch")) {
console.log("Task changed:", change);
}
accessor.channel(name)
Session-scoped channel for app-defined pub/sub.
const conv = client.subscribe("conv-123");
const todos = conv.channel("todos");
todos.subscribe((payload) => {
console.log("Todo update:", payload);
});
await todos.publish("add", { title: "Buy milk" });
Global Events
client.onEvent(handler)
Receives events from all subscribed sessions. Events include sessionId for routing.
client.onEvent((event) => {
console.log(event.sessionId, event.type);
});
client.on(type, handler)
Convenience subscription by event type (e.g., "content_delta", "tool_call").
Streaming Text
client.onStreamingText((state) => {
console.log(state.text, state.isStreaming);
});
Tool Confirmation UI
const session = client.subscribe("conv-123");
session.onToolConfirmation((request, respond) => {
const approved = window.confirm(`Allow ${request.name}?`);
respond({
approved,
reason: approved ? undefined : "User denied",
});
});
Error Handling
client.onConnectionChange((state) => {
if (state === "error") {
console.error("Connection error");
}
});
LineEditor
Framework-agnostic line editor with readline-quality editing: cursor movement, word navigation, kill/yank, history, and a match-based completion engine.
import { LineEditor } from "@agentick/client";
const editor = new LineEditor({ onSubmit: (text) => console.log(text) });
editor.handleInput(null, "hello");
editor.handleInput("ctrl+a", "");
editor.handleInput("return", "");
Completion
Register completion sources with match/resolve. The source's match function decides when to activate based on the current buffer and cursor; resolve produces items.
const unregister = editor.registerCompletion({
id: "file",
match({ value, cursor }) {
const idx = value.lastIndexOf("#", cursor - 1);
if (idx < 0) return null;
return { from: idx, query: value.slice(idx + 1, cursor) };
},
resolve: async ({ query }) => searchFiles(query),
debounce: 150,
});
When active, editor.state.completion contains the current CompletionState (items, selectedIndex, query, loading, from). Accepted completions are tracked as CompletedRange entries in editor.state.completedRanges.
See COMPLETION.md for the full completion system reference covering match/resolve API, resolution paths, keybindings, range tracking, slash commands, and custom sources.
Chat Primitives
Composable building blocks for chat UIs. Use ChatSession for the common case, or compose individual primitives for custom architectures.
ChatSession
Complete chat controller — messages, steering, tool confirmations, and attachments in one snapshot.
import { ChatSession } from "@agentick/client";
const chat = new ChatSession(client, {
sessionId: "conv-123",
autoSubscribe: true,
initialMessages: [],
transform: undefined,
renderMode: undefined,
confirmationPolicy: undefined,
deriveMode: undefined,
onEvent: undefined,
attachments: undefined,
});
chat.messages;
chat.chatMode;
chat.toolConfirmation;
chat.lastSubmitted;
chat.queued;
chat.isExecuting;
chat.mode;
chat.state.attachments;
chat.attachments.add({ name: "photo.png", mimeType: "image/png", source: base64Data });
chat.attachments.remove(id);
chat.attachments.clear();
chat.attachments.count;
chat.attachments.isEmpty;
chat.submit("Describe this image");
chat.steer("Force send");
chat.queue("Later");
chat.interrupt("Stop");
chat.flush();
chat.respondToConfirmation({ approved: true });
chat.clearMessages();
chat.prependMessages(olderMessages);
chat.appendMessages(newMessages);
const unsub = chat.onStateChange(() => {
console.log(chat.state);
});
chat.destroy();
Render Modes
Control how progressively messages appear in the message list. Without renderMode, messages only appear at execution_end (the entire execution must finish). With a render mode, content appears earlier:
"streaming" | Token-by-token | content_delta, reasoning_delta, tool_call_delta |
"block" | Full blocks | content, reasoning, tool_call |
"message" | Full message | message |
| (none) | Execution end | execution_end |
Each tier is a superset of the one above — "streaming" handles everything "block" does, plus finer deltas.
const chat = new ChatSession(client, {
sessionId: "conv-123",
renderMode: "block",
});
const chat = new ChatSession(client, {
sessionId: "conv-123",
renderMode: "streaming",
});
When renderMode is set, user messages appear immediately on submit() (before the server responds). Reasoning blocks are included in progressive rendering for all modes.
Custom Chat Modes
The deriveMode option lets you define custom chat mode enums:
type MyMode = "idle" | "working" | "needs_approval" | "error";
const chat = new ChatSession<MyMode>(client, {
sessionId: "conv-123",
deriveMode: ({ isExecuting, hasPendingConfirmation }) => {
if (hasPendingConfirmation) return "needs_approval";
if (isExecuting) return "working";
return "idle";
},
});
chat.chatMode;
Confirmation Policy
Auto-approve safe tools, prompt for dangerous ones:
const chat = new ChatSession(client, {
sessionId: "conv-123",
confirmationPolicy: (request) => {
if (["read_file", "glob", "grep"].includes(request.name)) {
return { action: "approve" };
}
if (request.name === "rm") {
return { action: "deny", reason: "Destructive operation" };
}
return { action: "prompt" };
},
});
Attachments
Send images, PDFs, and other files alongside text. Platforms add files to the attachment manager, and submit() drains them into the user message automatically.
chat.attachments.add({
name: "screenshot.png",
mimeType: "image/png",
source: base64String,
size: 102400,
});
chat.submit("What's in this image?");
The default validator accepts image/png, image/jpeg, image/gif, image/webp, and application/pdf. Customize via options:
import { defaultAttachmentValidator } from "@agentick/client";
const chat = new ChatSession(client, {
sessionId: "conv-123",
attachments: {
maxAttachments: 5,
validator: (input) => {
if (input.size && input.size > 10_000_000) {
return { valid: false, reason: "File too large (max 10MB)" };
}
return defaultAttachmentValidator(input);
},
toBlock: undefined,
},
});
Source strings are auto-detected: https://, http://, data:, and blob: prefixes produce URL sources; everything else is treated as base64 data.
Individual Primitives
For custom architectures, use the primitives directly. Each supports standalone mode (self-subscribes) or composed mode (subscribe: false + manual processEvent()).
MessageLog
Accumulates messages from execution lifecycle events with tool duration tracking.
import { MessageLog } from "@agentick/client";
const log = new MessageLog(client, {
sessionId: "conv-123",
initialMessages: [],
transform: undefined,
renderMode: "block",
});
log.messages;
log.pushUserMessage("Hello");
log.prependMessages(olderMessages);
log.appendMessages(newMessages);
log.clear();
log.destroy();
ToolConfirmations
Manages tool confirmation lifecycle with policy-based auto-resolution.
import { ToolConfirmations } from "@agentick/client";
const tc = new ToolConfirmations(client, {
sessionId: "conv-123",
policy: (req) => (req.name === "read_file" ? { action: "approve" } : { action: "prompt" }),
});
tc.pending;
tc.respond({ approved: true });
tc.destroy();
MessageSteering
Input-side message routing with queue/steer modes and auto-flush.
import { MessageSteering } from "@agentick/client";
const steering = new MessageSteering(client, {
sessionId: "conv-123",
mode: "queue",
flushMode: "sequential",
autoFlush: true,
});
steering.submit("Hello");
steering.steer("Now");
steering.queue("Later");
steering.flush();
steering.destroy();
Composition Pattern
All primitives support subscribe: false for parent-controlled event fan-out:
const log = new MessageLog(client, { sessionId: "s1", subscribe: false });
const tc = new ToolConfirmations(client, { sessionId: "s1", subscribe: false });
const steering = new MessageSteering(client, { sessionId: "s1", subscribe: false });
const accessor = client.session("s1");
accessor.onEvent((event) => {
steering.processEvent(event);
log.processEvent(event);
});
accessor.onToolConfirmation((request, respond) => {
tc.handleConfirmation(request, respond);
});
This is exactly what ChatSession does internally.
Transforms
import { timelineToMessages, extractToolCalls, defaultDeriveMode } from "@agentick/client";
const messages = timelineToMessages(entries, toolDurations);
const toolCalls = extractToolCalls(contentBlocks);
const mode = defaultDeriveMode({ isExecuting: true, hasPendingConfirmation: false });
Browser Support
- Chrome 89+
- Firefox 90+
- Safari 15+
- Edge 89+
Cleanup
client.destroy();