Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@syncagent/js

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@syncagent/js - npm Package Compare versions

Comparing version
0.3.3
to
0.3.4
+6
-0
dist/index.d.mts

@@ -161,2 +161,6 @@ interface ToolParameter {

}) => void;
/** Called for each complete message (user or assistant) — useful for logging/analytics */
onMessage?: (message: Message) => void;
/** Extra context injected into the request — merged with auto-detected page context */
context?: Record<string, any>;
signal?: AbortSignal;

@@ -195,5 +199,7 @@ }

constructor(config: SyncAgentConfig);
private validateConnectionString;
private headers;
private toWireMessages;
private serializeTools;
private buildRequestBody;
chat(messages: Message[], options?: ChatOptions & {

@@ -200,0 +206,0 @@ context?: Record<string, any>;

@@ -161,2 +161,6 @@ interface ToolParameter {

}) => void;
/** Called for each complete message (user or assistant) — useful for logging/analytics */
onMessage?: (message: Message) => void;
/** Extra context injected into the request — merged with auto-detected page context */
context?: Record<string, any>;
signal?: AbortSignal;

@@ -195,5 +199,7 @@ }

constructor(config: SyncAgentConfig);
private validateConnectionString;
private headers;
private toWireMessages;
private serializeTools;
private buildRequestBody;
chat(messages: Message[], options?: ChatOptions & {

@@ -200,0 +206,0 @@ context?: Record<string, any>;

+50
-34

@@ -117,4 +117,7 @@ "use strict";

if (!config.connectionString && !config.toolsOnly) throw new Error("SyncAgent: connectionString is required");
if (config.connectionString && !config.toolsOnly) {
this.validateConnectionString(config.connectionString);
}
this.apiKey = config.apiKey;
this.baseUrl = (SYNCAGENT_API_URL || config.baseUrl).replace(/\/$/, "");
this.baseUrl = (config.baseUrl || SYNCAGENT_API_URL).replace(/\/$/, "");
this.connectionString = config.connectionString || "";

@@ -134,2 +137,10 @@ this.tools = config.tools || {};

}
validateConnectionString(cs) {
const valid = cs.startsWith("mongodb") || cs.startsWith("postgresql://") || cs.startsWith("postgres://") || cs.startsWith("mysql://") || cs.startsWith("Server=") || cs.startsWith("server=") || cs.startsWith("https://") || cs.startsWith("file:") || cs.startsWith("/") || /^[A-Z]:\\/.test(cs);
if (!valid) {
console.warn(
`SyncAgent: connectionString format not recognized. Expected MongoDB, PostgreSQL, MySQL, SQLite, SQL Server, or Supabase URL. Got: "${cs.slice(0, 30)}..."`
);
}
}
headers() {

@@ -156,2 +167,18 @@ return {

}
buildRequestBody(wireMessages, clientTools, context) {
return JSON.stringify({
messages: wireMessages,
connectionString: this.connectionString,
...this.filter && Object.keys(this.filter).length > 0 && { filter: this.filter },
...this.operations && { operations: this.operations },
...clientTools && { clientTools },
...this.toolsOnly && { toolsOnly: true },
...context && { context },
...this.systemInstruction && { systemInstruction: this.systemInstruction },
...this.confirmWrites && { confirmWrites: true },
...this.language && { language: this.language },
...this.maxResults && { maxResults: this.maxResults },
...this.sensitiveFields && { sensitiveFields: this.sensitiveFields }
});
}
async chat(messages, options = {}) {

@@ -177,2 +204,3 @@ const { onToken, onComplete, onError, onToolCall, onStatus, onData, onAction, signal, context } = options;

for (let round = 0; round < maxRounds; round++) {
const body = this.buildRequestBody(wireMessages, clientTools, context);
let res;

@@ -183,16 +211,3 @@ try {

headers: this.headers(),
body: JSON.stringify({
messages: wireMessages,
connectionString: this.connectionString,
...this.filter && Object.keys(this.filter).length > 0 && { filter: this.filter },
...this.operations && { operations: this.operations },
...clientTools && { clientTools },
...this.toolsOnly && { toolsOnly: true },
...context && { context },
...this.systemInstruction && { systemInstruction: this.systemInstruction },
...this.confirmWrites && { confirmWrites: true },
...this.language && { language: this.language },
...this.maxResults && { maxResults: this.maxResults },
...this.sensitiveFields && { sensitiveFields: this.sensitiveFields }
}),
body,
signal: opts.signal

@@ -207,16 +222,3 @@ });

headers: this.headers(),
body: JSON.stringify({
messages: wireMessages,
connectionString: this.connectionString,
...this.filter && Object.keys(this.filter).length > 0 && { filter: this.filter },
...this.operations && { operations: this.operations },
...clientTools && { clientTools },
...this.toolsOnly && { toolsOnly: true },
...context && { context },
...this.systemInstruction && { systemInstruction: this.systemInstruction },
...this.confirmWrites && { confirmWrites: true },
...this.language && { language: this.language },
...this.maxResults && { maxResults: this.maxResults },
...this.sensitiveFields && { sensitiveFields: this.sensitiveFields }
}),
body,
signal: opts.signal

@@ -313,7 +315,21 @@ });

async getSchema() {
const res = await fetch(`${this.baseUrl}/api/v1/schema`, {
method: "POST",
headers: this.headers(),
body: JSON.stringify({ connectionString: this.connectionString })
});
let res;
try {
res = await fetch(`${this.baseUrl}/api/v1/schema`, {
method: "POST",
headers: this.headers(),
body: JSON.stringify({ connectionString: this.connectionString })
});
} catch (networkErr) {
try {
await new Promise((r) => setTimeout(r, 1e3));
res = await fetch(`${this.baseUrl}/api/v1/schema`, {
method: "POST",
headers: this.headers(),
body: JSON.stringify({ connectionString: this.connectionString })
});
} catch (retryErr) {
throw new Error(`Network error: ${retryErr.message || "Failed to connect to SyncAgent"}`);
}
}
if (!res.ok) {

@@ -320,0 +336,0 @@ const err = await res.json().catch(() => ({ error: res.statusText }));

@@ -90,4 +90,7 @@ // src/stream.ts

if (!config.connectionString && !config.toolsOnly) throw new Error("SyncAgent: connectionString is required");
if (config.connectionString && !config.toolsOnly) {
this.validateConnectionString(config.connectionString);
}
this.apiKey = config.apiKey;
this.baseUrl = (SYNCAGENT_API_URL || config.baseUrl).replace(/\/$/, "");
this.baseUrl = (config.baseUrl || SYNCAGENT_API_URL).replace(/\/$/, "");
this.connectionString = config.connectionString || "";

@@ -107,2 +110,10 @@ this.tools = config.tools || {};

}
validateConnectionString(cs) {
const valid = cs.startsWith("mongodb") || cs.startsWith("postgresql://") || cs.startsWith("postgres://") || cs.startsWith("mysql://") || cs.startsWith("Server=") || cs.startsWith("server=") || cs.startsWith("https://") || cs.startsWith("file:") || cs.startsWith("/") || /^[A-Z]:\\/.test(cs);
if (!valid) {
console.warn(
`SyncAgent: connectionString format not recognized. Expected MongoDB, PostgreSQL, MySQL, SQLite, SQL Server, or Supabase URL. Got: "${cs.slice(0, 30)}..."`
);
}
}
headers() {

@@ -129,2 +140,18 @@ return {

}
buildRequestBody(wireMessages, clientTools, context) {
return JSON.stringify({
messages: wireMessages,
connectionString: this.connectionString,
...this.filter && Object.keys(this.filter).length > 0 && { filter: this.filter },
...this.operations && { operations: this.operations },
...clientTools && { clientTools },
...this.toolsOnly && { toolsOnly: true },
...context && { context },
...this.systemInstruction && { systemInstruction: this.systemInstruction },
...this.confirmWrites && { confirmWrites: true },
...this.language && { language: this.language },
...this.maxResults && { maxResults: this.maxResults },
...this.sensitiveFields && { sensitiveFields: this.sensitiveFields }
});
}
async chat(messages, options = {}) {

@@ -150,2 +177,3 @@ const { onToken, onComplete, onError, onToolCall, onStatus, onData, onAction, signal, context } = options;

for (let round = 0; round < maxRounds; round++) {
const body = this.buildRequestBody(wireMessages, clientTools, context);
let res;

@@ -156,16 +184,3 @@ try {

headers: this.headers(),
body: JSON.stringify({
messages: wireMessages,
connectionString: this.connectionString,
...this.filter && Object.keys(this.filter).length > 0 && { filter: this.filter },
...this.operations && { operations: this.operations },
...clientTools && { clientTools },
...this.toolsOnly && { toolsOnly: true },
...context && { context },
...this.systemInstruction && { systemInstruction: this.systemInstruction },
...this.confirmWrites && { confirmWrites: true },
...this.language && { language: this.language },
...this.maxResults && { maxResults: this.maxResults },
...this.sensitiveFields && { sensitiveFields: this.sensitiveFields }
}),
body,
signal: opts.signal

@@ -180,16 +195,3 @@ });

headers: this.headers(),
body: JSON.stringify({
messages: wireMessages,
connectionString: this.connectionString,
...this.filter && Object.keys(this.filter).length > 0 && { filter: this.filter },
...this.operations && { operations: this.operations },
...clientTools && { clientTools },
...this.toolsOnly && { toolsOnly: true },
...context && { context },
...this.systemInstruction && { systemInstruction: this.systemInstruction },
...this.confirmWrites && { confirmWrites: true },
...this.language && { language: this.language },
...this.maxResults && { maxResults: this.maxResults },
...this.sensitiveFields && { sensitiveFields: this.sensitiveFields }
}),
body,
signal: opts.signal

@@ -286,7 +288,21 @@ });

async getSchema() {
const res = await fetch(`${this.baseUrl}/api/v1/schema`, {
method: "POST",
headers: this.headers(),
body: JSON.stringify({ connectionString: this.connectionString })
});
let res;
try {
res = await fetch(`${this.baseUrl}/api/v1/schema`, {
method: "POST",
headers: this.headers(),
body: JSON.stringify({ connectionString: this.connectionString })
});
} catch (networkErr) {
try {
await new Promise((r) => setTimeout(r, 1e3));
res = await fetch(`${this.baseUrl}/api/v1/schema`, {
method: "POST",
headers: this.headers(),
body: JSON.stringify({ connectionString: this.connectionString })
});
} catch (retryErr) {
throw new Error(`Network error: ${retryErr.message || "Failed to connect to SyncAgent"}`);
}
}
if (!res.ok) {

@@ -293,0 +309,0 @@ const err = await res.json().catch(() => ({ error: res.statusText }));

{
"name": "@syncagent/js",
"version": "0.3.3",
"version": "0.3.4",
"description": "SyncAgent JavaScript SDK — AI database agent for any app",
"homepage": "https://syncagentdev.vercel.app/docs",
"repository": {
"type": "git",
"url": "https://github.com/syncagent/syncagent",
"directory": "packages/js"
},
"main": "./dist/index.js",

@@ -6,0 +12,0 @@ "module": "./dist/index.mjs",

+141
-253
# @syncagent/js
Core JavaScript/TypeScript SDK for [SyncAgent](https://syncagent.dev) — add an AI database agent to any app.
Core JavaScript/TypeScript SDK for [SyncAgent](https://syncagentdev.vercel.app) — add an AI database agent to any app.
Works with **MongoDB**, **PostgreSQL**, **MySQL**, **SQLite**, **SQL Server**, and **Supabase**.
[![npm version](https://img.shields.io/npm/v/@syncagent/js)](https://www.npmjs.com/package/@syncagent/js)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
## Get Your API Key
1. [Sign up](https://syncagentdev.vercel.app/auth/register) for a free account
2. Go to your [Dashboard](https://syncagentdev.vercel.app/dashboard) → **New Project** → choose your database type
3. Copy your API key (starts with `sa_`)
> Every new project gets a **14-day trial** with 500 free requests — no credit card required. After the trial, you get 100 free requests/month on the Free plan.
## Install

@@ -19,11 +32,32 @@

connectionString: process.env.DATABASE_URL,
// baseUrl: "http://localhost:3100", // dev only — defaults to https://syncagent.dev
});
// Non-streaming
const result = await agent.chat([
{ role: "user", content: "How many users do we have?" }
{ role: "user", content: "How many users signed up this month?" }
]);
console.log(result.text);
// Streaming
await agent.chat(
[{ role: "user", content: "Show me the top 10 customers by revenue" }],
{
onToken: (token) => process.stdout.write(token),
onComplete: (text) => console.log("\nDone"),
onError: (err) => console.error(err),
}
);
```
## Supported Databases
| Database | Connection String Format |
|----------|------------------------|
| MongoDB | `mongodb+srv://user:pass@cluster.mongodb.net/mydb` |
| PostgreSQL | `postgresql://user:pass@host:5432/mydb` |
| MySQL | `mysql://user:pass@host:3306/mydb` |
| SQLite | `/absolute/path/to/database.sqlite` |
| SQL Server | `Server=host,1433;Database=mydb;User Id=user;Password=pass;Encrypt=true;` |
| Supabase | `https://xxx.supabase.co\|your-anon-key` |
## Configuration

@@ -35,19 +69,19 @@

| Option | Type | Required | Description |
| ------------------ | -------------------------------- | -------- | -------------------------------------------------------------- |
| `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. |
| `operations` | `("read"\|"create"\|"update"\|"delete")[]` | — | Restrict which operations are allowed for this session. Must be a subset of the project's configured operations. |
| `toolsOnly` | `boolean` | — | When `true`, disables all built-in DB tools — the agent only uses your custom `tools`. `connectionString` becomes optional. |
| `autoDetectPage` | `boolean` | `true` | Auto-detect current page from `window.location` and include in context. Set `false` to disable. |
| `systemInstruction`| `string` | — | Custom instructions for the agent — personality, tone, rules. Prepended to system prompt. |
| `confirmWrites` | `boolean` | `false` | When `true`, agent asks for explicit user confirmation before any create/update/delete. |
| `language` | `string` | — | Language the agent responds in. E.g. `"French"`, `"Spanish"`, `"Japanese"`. |
| `maxResults` | `number` | `50` | Default max records per query. Set lower for mobile, higher for dashboards. |
| `sensitiveFields` | `string[]` | `["password","token","secret"]` | Fields the agent masks as "••••••••" in responses. |
| `onBeforeToolCall` | `(name, args) => boolean` | — | Called before each client tool executes. Return `false` to block. |
| `onAfterToolCall` | `(name, args, result) => void` | — | Called after each client tool executes. Use for logging/analytics. |
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `apiKey` | `string` | ✅ | Your SyncAgent API key (`sa_...`) |
| `connectionString` | `string` | ✅* | Your database URL — sent at runtime, never stored. *Optional when `toolsOnly: true`. |
| `tools` | `Record<string, ToolDefinition>` | — | Custom tools the agent can call client-side |
| `baseUrl` | `string` | — | Override API URL (dev only) |
| `filter` | `Record<string, any>` | — | Mandatory query filter for multi-tenancy |
| `operations` | `("read"\|"create"\|"update"\|"delete")[]` | — | Restrict operations for this session |
| `toolsOnly` | `boolean` | — | Disables all DB tools — agent only uses your custom `tools` |
| `autoDetectPage` | `boolean` | `true` | Auto-detect current page from `window.location` |
| `systemInstruction` | `string` | — | Custom agent instructions — personality, tone, rules |
| `confirmWrites` | `boolean` | `false` | Ask for confirmation before create/update/delete |
| `language` | `string` | — | Language the agent responds in (e.g. `"French"`) |
| `maxResults` | `number` | `50` | Default max records per query |
| `sensitiveFields` | `string[]` | `["password","token","secret"]` | Fields masked in responses |
| `onBeforeToolCall` | `(name, args) => boolean` | — | Called before each client tool. Return `false` to block. |
| `onAfterToolCall` | `(name, args, result) => void` | — | Called after each client tool executes |

@@ -68,12 +102,12 @@ ## `client.chat(messages, options?)`

| Option | Type | Description |
| ------------ | ------------------------------------------------ | ---------------------------------------------- |
| `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 |
| Option | Type | Description |
|--------|------|-------------|
| `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` | Live status updates |
| `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 |

@@ -98,7 +132,20 @@ **Returns** `Promise<ChatResult>` → `{ text: string }`

## Context injection
## Multi-turn Conversations
The SDK **automatically detects** the current page from `window.location` on every message. The agent knows what page the user is on, what record they're viewing, and any relevant query params — with zero developer code.
```typescript
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 show their total orders" });
const r2 = await agent.chat(history);
```
## Context & Auto Page Detection
The SDK automatically detects the current page from `window.location` on every message — zero config needed.
```
URL: /dashboard/orders/ord_123?tab=details

@@ -113,46 +160,18 @@

The user can now say "show me this order" and the agent knows to query `ord_123`.
Pass additional context:
You can also pass **additional context** that merges on top of the auto-detected values:
```typescript
await agent.chat(messages, {
context: {
userId: "user_123",
userRole: "admin",
orgName: "Acme Corp",
}
context: { userId: "user_123", userRole: "admin", orgName: "Acme Corp" }
});
```
To disable auto-detection:
## `onData` — React to Query Results
```typescript
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
autoDetectPage: false, // only use manually passed context
});
```
Context is sent as a dedicated field in the request body and injected into the AI system prompt — not appended to the user's message text.
## `onData` callback
React to structured query results in your own UI:
```typescript
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
});
await agent.chat(messages, {
onData: (data) => {
// data.tool — "query_documents" | "aggregate_documents"
// data.collection — e.g. "orders"
// data.data — array of result rows
// data.count — number of results
console.log(`Got ${data.count} rows from ${data.collection}`);
setTableData(data.data); // update your own table component
console.log(data.collection); // "orders"
console.log(data.data); // array of result rows
console.log(data.count); // number of results
}

@@ -164,2 +183,4 @@ });

Give the agent capabilities beyond your database. Tools run entirely in your app — SyncAgent only sees the schema and result.
```typescript

@@ -186,10 +207,7 @@ const agent = new SyncAgentClient({

## Multi-tenant SaaS — scoping queries per organization
## Multi-tenant SaaS
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.
Pass `filter` to scope every agent operation to the current user's organization. Enforced server-side.
```typescript
import { SyncAgentClient } from "@syncagent/js";
// Scope to the current user's organization
const agent = new SyncAgentClient({

@@ -199,103 +217,20 @@ apiKey: "sa_your_key",

filter: { organizationId: currentUser.orgId },
operations: currentUser.isAdmin
? ["read", "create", "update", "delete"]
: ["read"],
});
// Every query is now automatically scoped:
// "Show all orders" → db.orders.find({ organizationId: "org_123" })
// "Count users" → db.users.countDocuments({ organizationId: "org_123" })
// "Add a product" → db.products.insertOne({ ...doc, organizationId: "org_123" })
```
**Without `filter`** — agent queries everything (suitable for single-tenant apps or admin tools).
## Tools-only Mode
**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.
Build an AI assistant powered by your own APIs — no database access needed.
Common patterns:
```typescript
// By organization ID
filter: { organizationId: org.id }
// By tenant slug
filter: { tenant: "acme-corp" }
// By user ID (personal data)
filter: { userId: currentUser.id }
// Multiple fields
filter: { orgId: org.id, deleted: false }
// SQL databases (same syntax)
filter: { tenant_id: tenant.id }
```
## Multi-turn conversations
```typescript
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
```typescript
const controller = new AbortController();
agent.chat(messages, { signal: controller.signal });
controller.abort(); // cancel at any time
```
## TypeScript types
```typescript
import type {
SyncAgentConfig, Message, ChatOptions, ChatResult,
CollectionSchema, SchemaField, ToolDefinition, ToolParameter, ToolData,
} from "@syncagent/js";
```
## License
MIT
## Per-user operation restrictions
Use `operations` to give different users different levels of access within the same project. The client can only restrict further — it can never grant more than what the project dashboard allows.
```typescript
// Read-only for regular users
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
filter: { organizationId: currentUser.orgId },
operations: ["read"],
});
// Full access for admins
const adminAgent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
filter: { organizationId: currentUser.orgId },
operations: ["read", "create", "update", "delete"],
});
```
## Tools-only mode
When you want the agent to **only** use your custom tools — with no database access at all — set `toolsOnly: true`. This is useful when you want to build an AI assistant powered by your own APIs, webhooks, or business logic.
```typescript
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
toolsOnly: true, // no connectionString needed
toolsOnly: true,
tools: {
searchProducts: {
description: "Search products by name or category",
inputSchema: {
query: { type: "string", description: "Search query" },
},
inputSchema: { query: { type: "string", description: "Search query" } },
execute: async ({ query }) => {

@@ -306,125 +241,78 @@ const res = await fetch(`/api/products?q=${query}`);

},
createOrder: {
description: "Create a new order for a customer",
inputSchema: {
productId: { type: "string", description: "Product ID" },
quantity: { type: "number", description: "Quantity" },
},
execute: async ({ productId, quantity }) => {
const res = await fetch("/api/orders", {
method: "POST",
body: JSON.stringify({ productId, quantity }),
});
return res.json();
},
},
},
});
// The agent will ONLY call searchProducts and createOrder.
// No database queries, no schema discovery, no DB connection.
```
You can also combine `toolsOnly` with a `connectionString` if you want — the DB connection is simply skipped when `toolsOnly` is enabled.
## Abort / Cancel
## System Instructions
Customize the agent's personality, tone, domain knowledge, or rules:
```typescript
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
systemInstruction: "You are a friendly sales assistant for Acme Corp. Always suggest upsells when showing order data. Never mention competitor names.",
});
const controller = new AbortController();
agent.chat(messages, { signal: controller.signal });
controller.abort();
```
Instructions are prepended to the system prompt and take priority over default behavior.
## Express.js Integration
## Write Confirmation
```typescript
import express from "express";
import { SyncAgentClient } from "@syncagent/js";
Require explicit user confirmation before any create, update, or delete:
const app = express();
app.use(express.json());
```typescript
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
apiKey: process.env.SYNCAGENT_KEY,
connectionString: process.env.DATABASE_URL,
confirmWrites: true,
});
// User: "Add a new customer named John"
// Agent: "I'd like to create a record in customers: name=John. Should I proceed?"
// User: "yes"
// Agent: "✅ Created customer John"
```
app.post("/chat", async (req, res) => {
res.setHeader("Content-Type", "text/plain");
res.setHeader("Transfer-Encoding", "chunked");
## Response Language
Make the agent respond in any language:
```typescript
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
language: "French",
await agent.chat(req.body.messages, {
onToken: (token) => res.write(token),
onComplete: () => res.end(),
onError: (err) => { res.status(500).end(err.message); },
});
});
// User: "Show me all orders"
// Agent: "Voici toutes les commandes..."
app.listen(3000);
```
Field names and code stay in English so tools work correctly.
## TypeScript Types
## Max Results
Control the default query limit:
```typescript
// Mobile app
new SyncAgentClient({ ..., maxResults: 10 });
// Admin dashboard
new SyncAgentClient({ ..., maxResults: 100 });
import type {
SyncAgentConfig, Message, ChatOptions, ChatResult,
CollectionSchema, SchemaField, ToolDefinition, ToolParameter, ToolData,
} from "@syncagent/js";
```
## Sensitive Field Masking
## Security
Specify which fields the agent should mask in responses:
- Your database connection string is **never stored** on SyncAgent servers
- It's passed at runtime, used to process the request, and immediately discarded
- API keys are hashed with bcrypt — raw keys are never stored
- Rate limiting: max 5 concurrent requests per API key per 10 seconds
```typescript
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
sensitiveFields: ["ssn", "creditCard", "salary", "bankAccount"],
});
## Plans & Pricing
// Agent shows: ssn: "••••••••", salary: "••••••••"
```
| Plan | Requests/mo | Collections | Price |
|------|-------------|-------------|-------|
| Free (+ 14-day trial) | 100 (500 during trial) | 5 | GH₵0 |
| Starter | 5,000 | 20 | GH₵150/mo |
| Pro | 50,000 | Unlimited | GH₵500/mo |
| Enterprise | Unlimited | Unlimited | Custom |
Default: `["password", "token", "secret"]`.
[View full pricing →](https://syncagentdev.vercel.app/pricing)
## Middleware Hooks
## Resources
Intercept tool calls for logging, audit trails, or dynamic blocking:
- [Documentation](https://syncagentdev.vercel.app/docs)
- [Dashboard](https://syncagentdev.vercel.app/dashboard)
- [Pricing](https://syncagentdev.vercel.app/pricing)
- [Changelog](https://syncagentdev.vercel.app/changelog)
```typescript
const agent = new SyncAgentClient({
apiKey: "sa_your_key",
connectionString: process.env.DATABASE_URL,
tools: { /* your tools */ },
## License
// Block specific operations dynamically
onBeforeToolCall: (toolName, args) => {
console.log(`[Audit] ${toolName}`, args);
if (toolName === "deleteUser" && !currentUser.isAdmin) return false;
return true;
},
// Log every result
onAfterToolCall: (toolName, args, result) => {
analytics.track("tool_call", { tool: toolName, success: result.success });
},
});
```
When `onBeforeToolCall` returns `false`, the tool is blocked and the AI receives an error message explaining the operation was denied.
MIT