Connecting Clients
In shortHow the 10xgraph-client TypeScript SDK connects browser and Node.js apps to a 10xGraph API over REST, SSE, and WebSockets.
- 4 min read
- 9 sections
- Updated
- v0.9.2
- Markdown
@10xscale/agentflow-client is a typed HTTP wrapper that connects any browser or Node.js app to a running 10xGraph API. It handles REST, SSE streaming, WebSockets, auth headers, and client-side tool execution.
flowchart LR
subgraph "Browser / Node.js"
APP[Your App]
SDK[AgentFlowClient]
end
subgraph "10xGraph API"
REST[REST Endpoints]
GRAPH[Python Graph]
end
APP -->|invoke / stream / wsStream| SDK
SDK -->|HTTP POST| REST
REST --> GRAPH
GRAPH -->|StreamChunk SSE / WS| REST
REST -->|typed events| SDK
SDK -->|result| APP
Installation and setup
npm install @10xscale/agentflow-clientimport { AgentFlowClient, Message, StreamRequest } from '@10xscale/agentflow-client';
const client = new AgentFlowClient({
baseUrl: 'http://localhost:8000',
authToken: 'your-api-key', // sent as Authorization: Bearer
timeout: 30_000, // ms; default 5 min
debug: false,
});Invoke vs Stream
| Method | Transport | Returns | Use when |
|---|---|---|---|
client.invoke(messages, options?) |
HTTP POST | Promise<InvokeResult> |
You only need the final response |
client.stream(messages, options?) |
HTTP, NDJSON body | AsyncGenerator<StreamChunk> |
Real-time token-by-token display |
client.wsStream(messages, options?) |
WebSocket | AsyncGenerator<StreamChunk> |
Low-latency or bidirectional use |
import { Message, StreamEventType } from '@10xscale/agentflow-client';
// Invoke — wait for full response
const result = await client.invoke(
[Message.text_message('What is the capital of France?')],
{ config: { thread_id: 'thread-abc' } }
);
console.log(result.messages.at(-1)?.text());
// Stream — async-iterate over chunks as they arrive
const stream = client.stream(
[Message.text_message('Tell me a story.')],
{ config: { thread_id: 'thread-abc' } }
);
for await (const chunk of stream) {
if (chunk.event === StreamEventType.MESSAGE && chunk.message) {
process.stdout.write(chunk.message.text());
}
}StreamChunk event types
StreamEventType |
When it fires |
|---|---|
MESSAGE |
A new Message was produced (assistant turn or tool result) |
UPDATES |
A full state snapshot was emitted |
ERROR |
An error occurred during streaming |
Threads from the client
Pass the same thread_id on every call to resume a conversation. The server restores the full AgentState from the checkpointer automatically.
const THREAD = 'user-123-support';
// Turn 1
await client.invoke(
[Message.text_message('My order is late.')],
{ config: { thread_id: THREAD } }
);
// Turn 2 — full history is available to the agent
await client.invoke(
[Message.text_message('Can you check the status?')],
{ config: { thread_id: THREAD } }
);Thread management
// threads() filters by free-text search and paginates; it does not take a user_id.
const threads = await client.threads({ search: 'refund', offset: 0, limit: 20 });
const detail = await client.threadDetails('thread-abc');
const state = await client.threadState('thread-abc' as unknown as number); // typed number; see the threads reference
await client.deleteThread('thread-abc');Remote tools
Remote tools are the standout feature: tool schemas live on the server, execution runs in the client. This lets the agent call browser APIs (clipboard, geolocation, DOM state), access secrets that should never leave the client, or run integrations the server has no credentials for.
sequenceDiagram
participant C as TypeScript Client
participant API as 10xGraph API
participant G as Python Graph
API->>G: attach schemas from 10xgraph.json at startup
C->>C: register handler "read_clipboard"
C->>API: client.invoke(message)
API->>G: run graph (server knows schema, not handler)
G-->>API: RemoteToolCallBlock — needs client execution
API-->>C: paused — tool_call event
C->>C: run local handler → result
C->>API: ToolResultBlock
API->>G: continue graph with tool result
G-->>API: final assistant message
API-->>C: done
import { AgentFlowClient, Message, StreamEventType } from '@10xscale/agentflow-client';
const client = new AgentFlowClient({ baseUrl: 'http://localhost:8000' });
// Schema is declared under remote_tools in server 10xgraph.json.
client.registerToolHandler('read_clipboard', async () => ({
content: await navigator.clipboard.readText(),
}));
// invoke() and stream() both drive the tool loop for you: when the
// server asks for read_clipboard, the client runs the handler and sends the
// result back automatically.
const stream = client.stream([Message.text_message('What is on my clipboard?')], {
config: { thread_id: 'clipboard-demo' },
});
for await (const chunk of stream) {
if (chunk.event === StreamEventType.MESSAGE && chunk.message?.delta) {
display(chunk.message.text());
}
}No server changes needed. The server sees the tool schema and calls it; the client sees the call, runs the handler, and returns the result — all within the same stream connection.
Files from the client
Upload a file first, then reference the returned ID in a message:
import { AgentFlowClient, Message } from '@10xscale/agentflow-client';
const uploaded = await client.uploadFile(file);
await client.invoke([
Message.withFile('What is in this image?', uploaded.data.file_id, 'image/jpeg'),
]);Auth on the client
// Bearer token (JWT or opaque)
const client = new AgentFlowClient({
baseUrl: 'http://localhost:8000',
authToken: 'eyJhbGci...',
});
// To rotate the token, create a new client instance with the refreshed value.
// AgentFlowClient has no mutable setApiKey() method.The token is sent as Authorization: Bearer <token> on every request. The server validates it via the configured BaseAuth backend. See Serving Agents for the server-side auth setup.
Memory from the client
import { MemoryType } from '@10xscale/agentflow-client';
await client.storeMemory({
content: 'Prefers dark mode',
memory_type: MemoryType.SEMANTIC, // required
category: 'preferences', // required
metadata: {},
});
const results = await client.searchMemory({
query: 'UI preferences',
memory_type: MemoryType.SEMANTIC,
limit: 5,
});
console.log(results.data.results);
await client.forgetMemories({ memory_type: MemoryType.SEMANTIC, category: 'preferences' });Health check
// Resolves when the API is reachable; throws an AgentFlowError otherwise.
const pong = await client.ping();
console.log(pong.data); // 'pong'There is no user_id argument on the memory or thread methods. Scoping is a server-side concern: the authenticated user is derived from the request, and per-call scoping goes in the config object.
Go deeper
| Guide | Link |
|---|---|
| Build a chat UI with React | React agent tutorial |
| Server-side auth setup | Serving Agents |
| Register remote tools on the server | Agents and Tools |
| Full client API reference | API Reference |