@syncagent/js
Advanced tools
+6
-0
@@ -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>; |
+6
-0
@@ -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 })); |
+50
-34
@@ -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 })); |
+7
-1
| { | ||
| "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**. | ||
| [](https://www.npmjs.com/package/@syncagent/js) | ||
| [](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 |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
868
4.83%1
-50%4
-20%51394
-5.7%310
-26.54%8
33.33%