@syncagent/js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @syncagent/js
2
+
3
+ Core JavaScript SDK for SyncAgent — add an AI database agent to any app.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @syncagent/js
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { SyncAgentClient } from "@syncagent/js";
15
+
16
+ const agent = new SyncAgentClient({
17
+ apiKey: "sa_your_api_key",
18
+ baseUrl: "https://your-syncagent-instance.com",
19
+ connectionString: process.env.DATABASE_URL, // your DB — never stored on our servers
20
+ });
21
+
22
+ // Streaming chat
23
+ await agent.chat(
24
+ [{ role: "user", content: "Show me all active users" }],
25
+ {
26
+ onToken: (token) => process.stdout.write(token),
27
+ onComplete: (text) => console.log("\n\nDone:", text),
28
+ }
29
+ );
30
+
31
+ // Non-streaming
32
+ const result = await agent.chat([{ role: "user", content: "Count all orders" }]);
33
+ console.log(result.text);
34
+
35
+ // Get database schema
36
+ const schema = await agent.getSchema();
37
+ ```
38
+
39
+ ## API
40
+
41
+ ### `new SyncAgentClient(config)`
42
+
43
+ | Option | Type | Required | Description |
44
+ | ------------------ | ------ | -------- | ---------------------------------------------- |
45
+ | `apiKey` | string | ✅ | Your SyncAgent API key |
46
+ | `baseUrl` | string | ✅ | Your SyncAgent instance URL |
47
+ | `connectionString` | string | ✅ | Your database URL (sent at runtime, not stored) |
48
+
49
+ ### `client.chat(messages, options?)`
50
+
51
+ | Option | Type | Description |
52
+ | ------------ | -------------------------- | ------------------------ |
53
+ | `onToken` | `(token: string) => void` | Called on each text chunk |
54
+ | `onComplete` | `(text: string) => void` | Called when done |
55
+ | `onError` | `(error: Error) => void` | Called on error |
56
+ | `signal` | `AbortSignal` | For cancellation |
57
+
58
+ ### `client.getSchema()`
59
+
60
+ Returns `Promise<CollectionSchema[]>`.
@@ -0,0 +1,59 @@
1
+ interface ToolParameter {
2
+ type: "string" | "number" | "boolean" | "object" | "array";
3
+ description?: string;
4
+ required?: boolean;
5
+ enum?: string[];
6
+ }
7
+ interface ToolDefinition {
8
+ description: string;
9
+ inputSchema: Record<string, ToolParameter>;
10
+ execute: (args: Record<string, any>) => any | Promise<any>;
11
+ }
12
+ interface SyncAgentConfig {
13
+ apiKey: string;
14
+ /** Your database connection string — sent at runtime, never stored on SyncAgent servers */
15
+ connectionString: string;
16
+ /** Custom tools the AI agent can call — executed client-side in your app */
17
+ tools?: Record<string, ToolDefinition>;
18
+ }
19
+ interface Message {
20
+ role: "user" | "assistant";
21
+ content: string;
22
+ }
23
+ interface ChatOptions {
24
+ onToken?: (token: string) => void;
25
+ onComplete?: (text: string) => void;
26
+ onError?: (error: Error) => void;
27
+ onToolCall?: (toolName: string, args: Record<string, any>, result: any) => void;
28
+ signal?: AbortSignal;
29
+ }
30
+ interface ChatResult {
31
+ text: string;
32
+ }
33
+ interface SchemaField {
34
+ name: string;
35
+ type: string;
36
+ sampleValue?: any;
37
+ }
38
+ interface CollectionSchema {
39
+ name: string;
40
+ fields: SchemaField[];
41
+ documentCount?: number;
42
+ }
43
+
44
+ declare class SyncAgentClient {
45
+ private apiKey;
46
+ private baseUrl;
47
+ private connectionString;
48
+ private tools;
49
+ constructor(config: SyncAgentConfig);
50
+ private headers;
51
+ private toWireMessages;
52
+ /** Serialize tool definitions for the API (strips execute functions) */
53
+ private serializeTools;
54
+ chat(messages: Message[], options?: ChatOptions): Promise<ChatResult>;
55
+ private chatLoop;
56
+ getSchema(): Promise<CollectionSchema[]>;
57
+ }
58
+
59
+ export { type ChatOptions, type ChatResult, type CollectionSchema, type Message, type SchemaField, SyncAgentClient, type SyncAgentConfig, type ToolDefinition, type ToolParameter };
@@ -0,0 +1,59 @@
1
+ interface ToolParameter {
2
+ type: "string" | "number" | "boolean" | "object" | "array";
3
+ description?: string;
4
+ required?: boolean;
5
+ enum?: string[];
6
+ }
7
+ interface ToolDefinition {
8
+ description: string;
9
+ inputSchema: Record<string, ToolParameter>;
10
+ execute: (args: Record<string, any>) => any | Promise<any>;
11
+ }
12
+ interface SyncAgentConfig {
13
+ apiKey: string;
14
+ /** Your database connection string — sent at runtime, never stored on SyncAgent servers */
15
+ connectionString: string;
16
+ /** Custom tools the AI agent can call — executed client-side in your app */
17
+ tools?: Record<string, ToolDefinition>;
18
+ }
19
+ interface Message {
20
+ role: "user" | "assistant";
21
+ content: string;
22
+ }
23
+ interface ChatOptions {
24
+ onToken?: (token: string) => void;
25
+ onComplete?: (text: string) => void;
26
+ onError?: (error: Error) => void;
27
+ onToolCall?: (toolName: string, args: Record<string, any>, result: any) => void;
28
+ signal?: AbortSignal;
29
+ }
30
+ interface ChatResult {
31
+ text: string;
32
+ }
33
+ interface SchemaField {
34
+ name: string;
35
+ type: string;
36
+ sampleValue?: any;
37
+ }
38
+ interface CollectionSchema {
39
+ name: string;
40
+ fields: SchemaField[];
41
+ documentCount?: number;
42
+ }
43
+
44
+ declare class SyncAgentClient {
45
+ private apiKey;
46
+ private baseUrl;
47
+ private connectionString;
48
+ private tools;
49
+ constructor(config: SyncAgentConfig);
50
+ private headers;
51
+ private toWireMessages;
52
+ /** Serialize tool definitions for the API (strips execute functions) */
53
+ private serializeTools;
54
+ chat(messages: Message[], options?: ChatOptions): Promise<ChatResult>;
55
+ private chatLoop;
56
+ getSchema(): Promise<CollectionSchema[]>;
57
+ }
58
+
59
+ export { type ChatOptions, type ChatResult, type CollectionSchema, type Message, type SchemaField, SyncAgentClient, type SyncAgentConfig, type ToolDefinition, type ToolParameter };
package/dist/index.js ADDED
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ SyncAgentClient: () => SyncAgentClient
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/stream.ts
28
+ function parseDataStreamChunk(chunk) {
29
+ const tokens = [];
30
+ const lines = chunk.split("\n");
31
+ for (const line of lines) {
32
+ const trimmed = line.trim();
33
+ if (!trimmed || !trimmed.startsWith("0:")) continue;
34
+ try {
35
+ const parsed = JSON.parse(trimmed.substring(2));
36
+ if (typeof parsed === "string") tokens.push(parsed);
37
+ } catch {
38
+ }
39
+ }
40
+ return tokens;
41
+ }
42
+
43
+ // src/client.ts
44
+ var SYNCAGENT_API_URL = "https://syncagentdev.vercel.app";
45
+ var SyncAgentClient = class {
46
+ constructor(config) {
47
+ if (!config.apiKey) throw new Error("SyncAgent: apiKey is required");
48
+ if (!config.connectionString) throw new Error("SyncAgent: connectionString is required");
49
+ this.apiKey = config.apiKey;
50
+ this.baseUrl = SYNCAGENT_API_URL;
51
+ this.connectionString = config.connectionString;
52
+ this.tools = config.tools || {};
53
+ }
54
+ headers() {
55
+ return {
56
+ "Content-Type": "application/json",
57
+ Authorization: `Bearer ${this.apiKey}`
58
+ };
59
+ }
60
+ toWireMessages(messages) {
61
+ return messages.map((m) => ({
62
+ id: Math.random().toString(36).slice(2),
63
+ role: m.role,
64
+ parts: [{ type: "text", text: m.content }]
65
+ }));
66
+ }
67
+ /** Serialize tool definitions for the API (strips execute functions) */
68
+ serializeTools() {
69
+ if (!Object.keys(this.tools).length) return void 0;
70
+ const serialized = {};
71
+ for (const [name, def] of Object.entries(this.tools)) {
72
+ serialized[name] = { description: def.description, inputSchema: def.inputSchema };
73
+ }
74
+ return serialized;
75
+ }
76
+ async chat(messages, options = {}) {
77
+ const { onToken, onComplete, onError, onToolCall, signal } = options;
78
+ const wireMessages = this.toWireMessages(messages);
79
+ const clientTools = this.serializeTools();
80
+ try {
81
+ const fullText = await this.chatLoop(wireMessages, clientTools, { onToken, onToolCall, signal });
82
+ onComplete?.(fullText);
83
+ return { text: fullText };
84
+ } catch (e) {
85
+ const error = e instanceof Error ? e : new Error(String(e));
86
+ onError?.(error);
87
+ throw error;
88
+ }
89
+ }
90
+ async chatLoop(wireMessages, clientTools, opts, maxRounds = 10) {
91
+ let fullText = "";
92
+ for (let round = 0; round < maxRounds; round++) {
93
+ const res = await fetch(`${this.baseUrl}/api/v1/chat`, {
94
+ method: "POST",
95
+ headers: this.headers(),
96
+ body: JSON.stringify({
97
+ messages: wireMessages,
98
+ connectionString: this.connectionString,
99
+ ...clientTools && { clientTools }
100
+ }),
101
+ signal: opts.signal
102
+ });
103
+ if (!res.ok) {
104
+ const err = await res.json().catch(() => ({ error: res.statusText }));
105
+ throw new Error(err.error || `HTTP ${res.status}`);
106
+ }
107
+ const reader = res.body?.getReader();
108
+ if (!reader) throw new Error("No response body");
109
+ const decoder = new TextDecoder();
110
+ let roundText = "";
111
+ let pendingToolCalls = [];
112
+ while (true) {
113
+ const { done, value } = await reader.read();
114
+ if (done) break;
115
+ const chunk = decoder.decode(value, { stream: true });
116
+ for (const line of chunk.split("\n")) {
117
+ if (line.startsWith("a:")) {
118
+ try {
119
+ const data = JSON.parse(line.slice(2));
120
+ if (data.type === "client_tool_call") {
121
+ pendingToolCalls.push({ id: data.id, name: data.name, args: data.args });
122
+ continue;
123
+ }
124
+ } catch {
125
+ }
126
+ }
127
+ }
128
+ const tokens = parseDataStreamChunk(chunk);
129
+ for (const token of tokens) {
130
+ roundText += token;
131
+ fullText += token;
132
+ opts.onToken?.(token);
133
+ }
134
+ }
135
+ if (!pendingToolCalls.length) return fullText;
136
+ const toolResults = [];
137
+ for (const tc of pendingToolCalls) {
138
+ const toolDef = this.tools[tc.name];
139
+ if (!toolDef) {
140
+ toolResults.push({ id: tc.id, name: tc.name, result: { error: `Tool "${tc.name}" not found on client` } });
141
+ continue;
142
+ }
143
+ try {
144
+ const result = await toolDef.execute(tc.args);
145
+ opts.onToolCall?.(tc.name, tc.args, result);
146
+ toolResults.push({ id: tc.id, name: tc.name, result });
147
+ } catch (e) {
148
+ const errResult = { error: e.message || String(e) };
149
+ opts.onToolCall?.(tc.name, tc.args, errResult);
150
+ toolResults.push({ id: tc.id, name: tc.name, result: errResult });
151
+ }
152
+ }
153
+ wireMessages.push({
154
+ id: Math.random().toString(36).slice(2),
155
+ role: "tool",
156
+ parts: toolResults.map((tr) => ({
157
+ type: "tool-result",
158
+ toolCallId: tr.id,
159
+ toolName: tr.name,
160
+ result: tr.result
161
+ }))
162
+ });
163
+ }
164
+ return fullText;
165
+ }
166
+ async getSchema() {
167
+ const res = await fetch(`${this.baseUrl}/api/v1/schema`, {
168
+ method: "POST",
169
+ headers: this.headers(),
170
+ body: JSON.stringify({ connectionString: this.connectionString })
171
+ });
172
+ if (!res.ok) {
173
+ const err = await res.json().catch(() => ({ error: res.statusText }));
174
+ throw new Error(err.error || `HTTP ${res.status}`);
175
+ }
176
+ const data = await res.json();
177
+ return data.collections;
178
+ }
179
+ };
180
+ // Annotate the CommonJS export names for ESM import in node:
181
+ 0 && (module.exports = {
182
+ SyncAgentClient
183
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,156 @@
1
+ // src/stream.ts
2
+ function parseDataStreamChunk(chunk) {
3
+ const tokens = [];
4
+ const lines = chunk.split("\n");
5
+ for (const line of lines) {
6
+ const trimmed = line.trim();
7
+ if (!trimmed || !trimmed.startsWith("0:")) continue;
8
+ try {
9
+ const parsed = JSON.parse(trimmed.substring(2));
10
+ if (typeof parsed === "string") tokens.push(parsed);
11
+ } catch {
12
+ }
13
+ }
14
+ return tokens;
15
+ }
16
+
17
+ // src/client.ts
18
+ var SYNCAGENT_API_URL = "https://syncagentdev.vercel.app";
19
+ var SyncAgentClient = class {
20
+ constructor(config) {
21
+ if (!config.apiKey) throw new Error("SyncAgent: apiKey is required");
22
+ if (!config.connectionString) throw new Error("SyncAgent: connectionString is required");
23
+ this.apiKey = config.apiKey;
24
+ this.baseUrl = SYNCAGENT_API_URL;
25
+ this.connectionString = config.connectionString;
26
+ this.tools = config.tools || {};
27
+ }
28
+ headers() {
29
+ return {
30
+ "Content-Type": "application/json",
31
+ Authorization: `Bearer ${this.apiKey}`
32
+ };
33
+ }
34
+ toWireMessages(messages) {
35
+ return messages.map((m) => ({
36
+ id: Math.random().toString(36).slice(2),
37
+ role: m.role,
38
+ parts: [{ type: "text", text: m.content }]
39
+ }));
40
+ }
41
+ /** Serialize tool definitions for the API (strips execute functions) */
42
+ serializeTools() {
43
+ if (!Object.keys(this.tools).length) return void 0;
44
+ const serialized = {};
45
+ for (const [name, def] of Object.entries(this.tools)) {
46
+ serialized[name] = { description: def.description, inputSchema: def.inputSchema };
47
+ }
48
+ return serialized;
49
+ }
50
+ async chat(messages, options = {}) {
51
+ const { onToken, onComplete, onError, onToolCall, signal } = options;
52
+ const wireMessages = this.toWireMessages(messages);
53
+ const clientTools = this.serializeTools();
54
+ try {
55
+ const fullText = await this.chatLoop(wireMessages, clientTools, { onToken, onToolCall, signal });
56
+ onComplete?.(fullText);
57
+ return { text: fullText };
58
+ } catch (e) {
59
+ const error = e instanceof Error ? e : new Error(String(e));
60
+ onError?.(error);
61
+ throw error;
62
+ }
63
+ }
64
+ async chatLoop(wireMessages, clientTools, opts, maxRounds = 10) {
65
+ let fullText = "";
66
+ for (let round = 0; round < maxRounds; round++) {
67
+ const res = await fetch(`${this.baseUrl}/api/v1/chat`, {
68
+ method: "POST",
69
+ headers: this.headers(),
70
+ body: JSON.stringify({
71
+ messages: wireMessages,
72
+ connectionString: this.connectionString,
73
+ ...clientTools && { clientTools }
74
+ }),
75
+ signal: opts.signal
76
+ });
77
+ if (!res.ok) {
78
+ const err = await res.json().catch(() => ({ error: res.statusText }));
79
+ throw new Error(err.error || `HTTP ${res.status}`);
80
+ }
81
+ const reader = res.body?.getReader();
82
+ if (!reader) throw new Error("No response body");
83
+ const decoder = new TextDecoder();
84
+ let roundText = "";
85
+ let pendingToolCalls = [];
86
+ while (true) {
87
+ const { done, value } = await reader.read();
88
+ if (done) break;
89
+ const chunk = decoder.decode(value, { stream: true });
90
+ for (const line of chunk.split("\n")) {
91
+ if (line.startsWith("a:")) {
92
+ try {
93
+ const data = JSON.parse(line.slice(2));
94
+ if (data.type === "client_tool_call") {
95
+ pendingToolCalls.push({ id: data.id, name: data.name, args: data.args });
96
+ continue;
97
+ }
98
+ } catch {
99
+ }
100
+ }
101
+ }
102
+ const tokens = parseDataStreamChunk(chunk);
103
+ for (const token of tokens) {
104
+ roundText += token;
105
+ fullText += token;
106
+ opts.onToken?.(token);
107
+ }
108
+ }
109
+ if (!pendingToolCalls.length) return fullText;
110
+ const toolResults = [];
111
+ for (const tc of pendingToolCalls) {
112
+ const toolDef = this.tools[tc.name];
113
+ if (!toolDef) {
114
+ toolResults.push({ id: tc.id, name: tc.name, result: { error: `Tool "${tc.name}" not found on client` } });
115
+ continue;
116
+ }
117
+ try {
118
+ const result = await toolDef.execute(tc.args);
119
+ opts.onToolCall?.(tc.name, tc.args, result);
120
+ toolResults.push({ id: tc.id, name: tc.name, result });
121
+ } catch (e) {
122
+ const errResult = { error: e.message || String(e) };
123
+ opts.onToolCall?.(tc.name, tc.args, errResult);
124
+ toolResults.push({ id: tc.id, name: tc.name, result: errResult });
125
+ }
126
+ }
127
+ wireMessages.push({
128
+ id: Math.random().toString(36).slice(2),
129
+ role: "tool",
130
+ parts: toolResults.map((tr) => ({
131
+ type: "tool-result",
132
+ toolCallId: tr.id,
133
+ toolName: tr.name,
134
+ result: tr.result
135
+ }))
136
+ });
137
+ }
138
+ return fullText;
139
+ }
140
+ async getSchema() {
141
+ const res = await fetch(`${this.baseUrl}/api/v1/schema`, {
142
+ method: "POST",
143
+ headers: this.headers(),
144
+ body: JSON.stringify({ connectionString: this.connectionString })
145
+ });
146
+ if (!res.ok) {
147
+ const err = await res.json().catch(() => ({ error: res.statusText }));
148
+ throw new Error(err.error || `HTTP ${res.status}`);
149
+ }
150
+ const data = await res.json();
151
+ return data.collections;
152
+ }
153
+ };
154
+ export {
155
+ SyncAgentClient
156
+ };
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@syncagent/js",
3
+ "version": "0.1.0",
4
+ "description": "SyncAgent JavaScript SDK — AI database agent for any app",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": ["dist"],
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "dev": "tsup --watch"
19
+ },
20
+ "devDependencies": {
21
+ "tsup": "^8.4.0",
22
+ "typescript": "^5.8.3"
23
+ },
24
+ "license": "MIT",
25
+ "keywords": ["syncagent", "ai", "database", "agent", "sdk", "chat"]
26
+ }