@syncagent/js
Core JavaScript/TypeScript SDK for SyncAgent — add an AI database agent to any app.
Install
npm install @syncagent/js
Quick Start
import { SyncAgentClient } from "@syncagent/js";
const agent = new SyncAgentClient({
apiKey: "sa_your_api_key",
connectionString: process.env.DATABASE_URL,
});
const result = await agent.chat([
{ role: "user", content: "How many users do we have?" }
]);
console.log(result.text);
Configuration
new SyncAgentClient(config: SyncAgentConfig)
apiKey | string | ✅ | Your SyncAgent API key (sa_...) |
connectionString | string | ✅ | Your database URL — sent at runtime, never stored |
tools | Record<string, ToolDefinition> | — | Custom tools the agent can call client-side |
baseUrl | string | — | Override API URL. Defaults to https://syncagent.dev |
filter | Record<string, any> | — | Mandatory query filter — scopes ALL operations to matching records. Use for multi-tenant SaaS. |
client.chat(messages, options?)
const result = await agent.chat(messages, options);
messages — Message[]
{ role: "user" | "assistant"; content: string }
options — ChatOptions
onToken | (token: string) => void | Called for each streamed text chunk |
onComplete | (text: string) => void | Called with the full response text |
onError | (error: Error) => void | Called on error |
onStatus | (step: string, label: string) => void | Called with live status updates while working |
onData | (data: ToolData) => void | Called when a DB tool returns structured data |
onToolCall | (name: string, args: any, result: any) => void | Called when a custom tool executes |
signal | AbortSignal | Cancel the request |
context | Record<string, any> | Extra context injected into every message |
Returns Promise<ChatResult> → { text: string }
Status steps
onStatus fires with these step values:
"connecting" — connecting to the database
"schema" — discovering schema
"thinking" — AI is reasoning
"querying" — executing a DB tool
"done" — complete
client.getSchema()
const schema = await agent.getSchema();
Context injection
Pass per-message context so the agent knows what the user is looking at:
await agent.chat(messages, {
context: {
userId: "user_123",
currentPage: "orders",
selectedOrderId: "ord_456",
}
});
onData callback
React to structured query results in your own UI:
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
});
await agent.chat(messages, {
onData: (data) => {
console.log(`Got ${data.count} rows from ${data.collection}`);
setTableData(data.data);
}
});
Custom Tools
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
tools: {
sendEmail: {
description: "Send an email to a user",
inputSchema: {
to: { type: "string", description: "Recipient email" },
subject: { type: "string", description: "Subject line" },
body: { type: "string", description: "Email body" },
},
execute: async ({ to, subject, body }) => {
await mailer.send({ to, subject, text: body });
return { sent: true };
},
},
},
});
Multi-tenant SaaS — scoping queries per organization
When building a SaaS app, multiple organizations share the same database. Pass filter to scope every agent operation to the current user's organization. The agent cannot query, insert, update, or delete outside this scope — it's enforced server-side.
import { SyncAgentClient } from "@syncagent/js";
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
filter: { organizationId: currentUser.orgId },
});
Without filter — agent queries everything (suitable for single-tenant apps or admin tools).
With filter — every read, write, update, and delete is strictly scoped. The agent is told about the scope in its system prompt and cannot override it.
Common patterns:
filter: { organizationId: org.id }
filter: { tenant: "acme-corp" }
filter: { userId: currentUser.id }
filter: { orgId: org.id, deleted: false }
filter: { tenant_id: tenant.id }
Multi-turn conversations
const history: Message[] = [];
history.push({ role: "user", content: "Show top 5 customers" });
const r1 = await agent.chat(history);
history.push({ role: "assistant", content: r1.text });
history.push({ role: "user", content: "Now email all of them" });
const r2 = await agent.chat(history);
Abort / cancel
const controller = new AbortController();
agent.chat(messages, { signal: controller.signal });
controller.abort();
TypeScript types
import type {
SyncAgentConfig, Message, ChatOptions, ChatResult,
CollectionSchema, SchemaField, ToolDefinition, ToolParameter, ToolData,
} from "@syncagent/js";
License
MIT