@yesvara/svara 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/LICENSE +21 -0
- package/README.md +497 -0
- package/dist/chunk-CIESM3BP.mjs +33 -0
- package/dist/chunk-FEA5KIJN.mjs +418 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +328 -0
- package/dist/cli/index.mjs +39 -0
- package/dist/dev-OYGXXK2B.mjs +69 -0
- package/dist/index.d.mts +967 -0
- package/dist/index.d.ts +967 -0
- package/dist/index.js +1976 -0
- package/dist/index.mjs +1502 -0
- package/dist/new-7K4NIDZO.mjs +177 -0
- package/dist/retriever-4QY667XF.mjs +7 -0
- package/examples/01-basic/index.ts +26 -0
- package/examples/02-with-tools/index.ts +73 -0
- package/examples/03-rag-knowledge/index.ts +41 -0
- package/examples/04-multi-channel/index.ts +91 -0
- package/package.json +74 -0
- package/src/app/index.ts +176 -0
- package/src/channels/telegram.ts +122 -0
- package/src/channels/web.ts +118 -0
- package/src/channels/whatsapp.ts +161 -0
- package/src/cli/commands/dev.ts +87 -0
- package/src/cli/commands/new.ts +213 -0
- package/src/cli/index.ts +78 -0
- package/src/core/agent.ts +607 -0
- package/src/core/llm.ts +406 -0
- package/src/core/types.ts +183 -0
- package/src/database/schema.ts +79 -0
- package/src/database/sqlite.ts +239 -0
- package/src/index.ts +94 -0
- package/src/memory/context.ts +49 -0
- package/src/memory/conversation.ts +51 -0
- package/src/rag/chunker.ts +165 -0
- package/src/rag/loader.ts +216 -0
- package/src/rag/retriever.ts +248 -0
- package/src/tools/executor.ts +54 -0
- package/src/tools/index.ts +89 -0
- package/src/tools/registry.ts +44 -0
- package/src/types.ts +131 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import "./chunk-CIESM3BP.mjs";
|
|
2
|
+
|
|
3
|
+
// src/cli/commands/new.ts
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
async function newProject(options) {
|
|
8
|
+
const { name, provider = "openai", channels = ["web"] } = options;
|
|
9
|
+
const targetDir = path.resolve(process.cwd(), name);
|
|
10
|
+
console.log(`
|
|
11
|
+
\u2728 Creating SvaraJS project: ${name}
|
|
12
|
+
`);
|
|
13
|
+
try {
|
|
14
|
+
await fs.access(targetDir);
|
|
15
|
+
console.error(`\u274C Directory "${name}" already exists.`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
20
|
+
await fs.mkdir(path.join(targetDir, "src"), { recursive: true });
|
|
21
|
+
await fs.mkdir(path.join(targetDir, "docs"), { recursive: true });
|
|
22
|
+
await fs.mkdir(path.join(targetDir, "data"), { recursive: true });
|
|
23
|
+
const files = {
|
|
24
|
+
"package.json": generatePackageJson(name),
|
|
25
|
+
"tsconfig.json": generateTsConfig(),
|
|
26
|
+
".env.example": generateEnvExample(provider, channels),
|
|
27
|
+
".gitignore": generateGitignore(),
|
|
28
|
+
"src/index.ts": generateIndexFile(name, provider, channels),
|
|
29
|
+
"docs/README.md": `# ${name} Knowledge Base
|
|
30
|
+
|
|
31
|
+
Add your documents here for RAG.
|
|
32
|
+
`
|
|
33
|
+
};
|
|
34
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
35
|
+
const fullPath = path.join(targetDir, filePath);
|
|
36
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
37
|
+
await fs.writeFile(fullPath, content, "utf-8");
|
|
38
|
+
console.log(` \u2713 ${filePath}`);
|
|
39
|
+
}
|
|
40
|
+
if (options.installDeps !== false) {
|
|
41
|
+
console.log("\n\u{1F4E6} Installing dependencies...\n");
|
|
42
|
+
try {
|
|
43
|
+
execSync("npm install", { cwd: targetDir, stdio: "inherit" });
|
|
44
|
+
} catch {
|
|
45
|
+
console.warn('\n\u26A0\uFE0F Dependency install failed. Run "npm install" manually.\n');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
console.log(`
|
|
49
|
+
\u2705 Project ready!
|
|
50
|
+
|
|
51
|
+
cd ${name}
|
|
52
|
+
cp .env.example .env # Add your API keys
|
|
53
|
+
npm run dev # Start the agent
|
|
54
|
+
|
|
55
|
+
\u{1F4DA} Docs: https://svarajs.dev
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
function generatePackageJson(name) {
|
|
59
|
+
return JSON.stringify({
|
|
60
|
+
name,
|
|
61
|
+
version: "0.1.0",
|
|
62
|
+
private: true,
|
|
63
|
+
scripts: {
|
|
64
|
+
dev: "tsx watch src/index.ts",
|
|
65
|
+
build: "tsc",
|
|
66
|
+
start: "node dist/index.js"
|
|
67
|
+
},
|
|
68
|
+
dependencies: {
|
|
69
|
+
svarajs: "latest",
|
|
70
|
+
dotenv: "^16.4.5"
|
|
71
|
+
},
|
|
72
|
+
devDependencies: {
|
|
73
|
+
"@types/node": "^20.14.2",
|
|
74
|
+
tsx: "^4.15.7",
|
|
75
|
+
typescript: "^5.4.5"
|
|
76
|
+
}
|
|
77
|
+
}, null, 2);
|
|
78
|
+
}
|
|
79
|
+
function generateTsConfig() {
|
|
80
|
+
return JSON.stringify({
|
|
81
|
+
compilerOptions: {
|
|
82
|
+
target: "ES2022",
|
|
83
|
+
module: "CommonJS",
|
|
84
|
+
moduleResolution: "bundler",
|
|
85
|
+
outDir: "dist",
|
|
86
|
+
rootDir: "src",
|
|
87
|
+
strict: true,
|
|
88
|
+
esModuleInterop: true,
|
|
89
|
+
skipLibCheck: true
|
|
90
|
+
},
|
|
91
|
+
include: ["src/**/*"],
|
|
92
|
+
exclude: ["node_modules", "dist"]
|
|
93
|
+
}, null, 2);
|
|
94
|
+
}
|
|
95
|
+
function generateEnvExample(provider, channels) {
|
|
96
|
+
const lines = ["# SvaraJS Environment Variables", ""];
|
|
97
|
+
if (provider === "openai") {
|
|
98
|
+
lines.push("# OpenAI", "OPENAI_API_KEY=sk-...", "");
|
|
99
|
+
} else if (provider === "anthropic") {
|
|
100
|
+
lines.push("# Anthropic", "ANTHROPIC_API_KEY=sk-ant-...", "");
|
|
101
|
+
}
|
|
102
|
+
if (channels.includes("telegram")) {
|
|
103
|
+
lines.push("# Telegram", "TELEGRAM_BOT_TOKEN=...", "");
|
|
104
|
+
}
|
|
105
|
+
if (channels.includes("whatsapp")) {
|
|
106
|
+
lines.push("# WhatsApp", "WA_ACCESS_TOKEN=...", "WA_PHONE_ID=...", "WA_VERIFY_TOKEN=...", "");
|
|
107
|
+
}
|
|
108
|
+
return lines.join("\n");
|
|
109
|
+
}
|
|
110
|
+
function generateIndexFile(name, provider, channels) {
|
|
111
|
+
const modelMap = {
|
|
112
|
+
openai: "gpt-4o",
|
|
113
|
+
anthropic: "claude-opus-4-6",
|
|
114
|
+
ollama: "llama3"
|
|
115
|
+
};
|
|
116
|
+
const channelSetup = channels.map((ch) => {
|
|
117
|
+
if (ch === "web") return `agent.use('web', { port: 3000, cors: true });`;
|
|
118
|
+
if (ch === "telegram") return `agent.use('telegram', { token: process.env.TELEGRAM_BOT_TOKEN! });`;
|
|
119
|
+
if (ch === "whatsapp") return [
|
|
120
|
+
`agent.use('whatsapp', {`,
|
|
121
|
+
` token: process.env.WA_ACCESS_TOKEN!,`,
|
|
122
|
+
` phoneId: process.env.WA_PHONE_ID!,`,
|
|
123
|
+
` verifyToken: process.env.WA_VERIFY_TOKEN!,`,
|
|
124
|
+
`});`
|
|
125
|
+
].join("\n");
|
|
126
|
+
return "";
|
|
127
|
+
}).filter(Boolean).join("\n");
|
|
128
|
+
return `import 'dotenv/config';
|
|
129
|
+
import { svara } from 'svarajs';
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* ${name} \u2014 powered by SvaraJS
|
|
133
|
+
*/
|
|
134
|
+
const agent = svara({
|
|
135
|
+
llm: {
|
|
136
|
+
provider: '${provider}',
|
|
137
|
+
model: '${modelMap[provider] ?? "gpt-4o"}',
|
|
138
|
+
},
|
|
139
|
+
systemPrompt: \`You are a helpful AI assistant called ${name}.
|
|
140
|
+
Be concise, friendly, and always try your best to help.\`,
|
|
141
|
+
memory: {
|
|
142
|
+
type: 'conversation',
|
|
143
|
+
maxMessages: 20,
|
|
144
|
+
},
|
|
145
|
+
verbose: true,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// \u2500\u2500 Tools \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
149
|
+
agent.tool('get_time', {
|
|
150
|
+
description: 'Get the current date and time',
|
|
151
|
+
parameters: {},
|
|
152
|
+
execute: async () => ({
|
|
153
|
+
datetime: new Date().toISOString(),
|
|
154
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// \u2500\u2500 Channels \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
159
|
+
${channelSetup}
|
|
160
|
+
|
|
161
|
+
// \u2500\u2500 Start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
162
|
+
agent.start().then(() => {
|
|
163
|
+
console.log('\u{1F680} ${name} is running!');
|
|
164
|
+
});
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
function generateGitignore() {
|
|
168
|
+
return `node_modules/
|
|
169
|
+
dist/
|
|
170
|
+
.env
|
|
171
|
+
data/*.db
|
|
172
|
+
*.log
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
export {
|
|
176
|
+
newProject
|
|
177
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @example Basic Agent
|
|
3
|
+
*
|
|
4
|
+
* The simplest possible SvaraJS agent.
|
|
5
|
+
* 10 lines. Works out of the box.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx tsx index.ts
|
|
8
|
+
*
|
|
9
|
+
* curl -X POST http://localhost:3000/chat \
|
|
10
|
+
* -H "Content-Type: application/json" \
|
|
11
|
+
* -d '{ "message": "Hello! What can you do?" }'
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import 'dotenv/config';
|
|
15
|
+
import { SvaraApp, SvaraAgent } from '@yesvara/svara';
|
|
16
|
+
|
|
17
|
+
const app = new SvaraApp({ cors: true });
|
|
18
|
+
|
|
19
|
+
const agent = new SvaraAgent({
|
|
20
|
+
name: 'Aria',
|
|
21
|
+
model: 'gpt-4o-mini',
|
|
22
|
+
systemPrompt: 'You are Aria, a friendly and helpful AI assistant. Keep responses concise.',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
app.route('/chat', agent.handler());
|
|
26
|
+
app.listen(3000);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @example Agent with Tools
|
|
3
|
+
*
|
|
4
|
+
* An agent that can call custom functions (tools).
|
|
5
|
+
* The LLM decides when to call each tool based on the conversation.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx tsx index.ts
|
|
8
|
+
*
|
|
9
|
+
* curl -X POST http://localhost:3000/chat \
|
|
10
|
+
* -H "Content-Type: application/json" \
|
|
11
|
+
* -d '{ "message": "What time is it? Also, what is 42 * 17?", "sessionId": "user-1" }'
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import 'dotenv/config';
|
|
15
|
+
import { SvaraApp, SvaraAgent, createTool } from '@yesvara/svara';
|
|
16
|
+
|
|
17
|
+
// ── Define Tools ──────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const getCurrentTime = createTool({
|
|
20
|
+
name: 'get_current_time',
|
|
21
|
+
description: 'Get the current date and time in a specific timezone',
|
|
22
|
+
parameters: {
|
|
23
|
+
timezone: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'IANA timezone name, e.g. "Asia/Jakarta", "America/New_York"',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
async run({ timezone = 'UTC' }) {
|
|
29
|
+
return {
|
|
30
|
+
datetime: new Date().toLocaleString('en-US', { timeZone: timezone as string }),
|
|
31
|
+
timezone,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const calculate = createTool({
|
|
37
|
+
name: 'calculate',
|
|
38
|
+
description: 'Evaluate a safe mathematical expression',
|
|
39
|
+
parameters: {
|
|
40
|
+
expression: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description: 'Math expression to evaluate, e.g. "42 * 17", "(100 + 50) / 3"',
|
|
43
|
+
required: true,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
async run({ expression }) {
|
|
47
|
+
// Very simple safe eval — in production use a proper math library
|
|
48
|
+
const result = Function(`"use strict"; return (${expression as string})`)() as number;
|
|
49
|
+
return { expression, result };
|
|
50
|
+
},
|
|
51
|
+
timeout: 5_000,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ── Create Agent ──────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
const agent = new SvaraAgent({
|
|
57
|
+
name: 'Calculator Bot',
|
|
58
|
+
model: 'gpt-4o-mini',
|
|
59
|
+
systemPrompt: 'You are a helpful assistant with access to real-time data and calculation tools.',
|
|
60
|
+
memory: { window: 10 },
|
|
61
|
+
tools: [getCurrentTime, calculate],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
const app = new SvaraApp({ cors: true });
|
|
67
|
+
app.route('/chat', agent.handler());
|
|
68
|
+
app.listen(3000);
|
|
69
|
+
|
|
70
|
+
// Observe tool usage
|
|
71
|
+
agent.on('tool:call', ({ tools }: { tools: string[] }) => {
|
|
72
|
+
console.log(`[Tools] Calling: ${tools.join(', ')}`);
|
|
73
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @example RAG Knowledge Base Agent
|
|
3
|
+
*
|
|
4
|
+
* An agent that answers questions from your documents.
|
|
5
|
+
* Drop files in ./docs and the agent knows everything in them.
|
|
6
|
+
*
|
|
7
|
+
* Supported: PDF, Markdown, TXT, DOCX, HTML, JSON
|
|
8
|
+
*
|
|
9
|
+
* Run: npx tsx index.ts
|
|
10
|
+
*
|
|
11
|
+
* curl -X POST http://localhost:3000/chat \
|
|
12
|
+
* -H "Content-Type: application/json" \
|
|
13
|
+
* -d '{ "message": "What is our refund policy?", "sessionId": "customer-42" }'
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import 'dotenv/config';
|
|
17
|
+
import { SvaraApp, SvaraAgent } from '@yesvara/svara';
|
|
18
|
+
|
|
19
|
+
const agent = new SvaraAgent({
|
|
20
|
+
name: 'Knowledge Base Bot',
|
|
21
|
+
model: 'gpt-4o-mini',
|
|
22
|
+
|
|
23
|
+
systemPrompt: `You are a helpful customer support agent.
|
|
24
|
+
Answer questions using the provided documentation.
|
|
25
|
+
If you don't know the answer, say so honestly — don't make things up.
|
|
26
|
+
Always be friendly and professional.`,
|
|
27
|
+
|
|
28
|
+
// Point to your docs folder — any file type, glob patterns work
|
|
29
|
+
knowledge: './docs/**/*',
|
|
30
|
+
|
|
31
|
+
memory: { window: 20 },
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// You can also add knowledge dynamically (hot reload, no restart needed)
|
|
35
|
+
// await agent.addKnowledge('./new-policy-2024.pdf');
|
|
36
|
+
|
|
37
|
+
const app = new SvaraApp({ cors: true });
|
|
38
|
+
app.route('/chat', agent.handler());
|
|
39
|
+
|
|
40
|
+
await agent.start(); // indexes documents
|
|
41
|
+
app.listen(3000);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @example Multi-Channel Agent
|
|
3
|
+
*
|
|
4
|
+
* One agent, three channels. Same conversations, everywhere.
|
|
5
|
+
* Customers can reach you on WhatsApp, Telegram, or the web —
|
|
6
|
+
* the agent handles all of them seamlessly.
|
|
7
|
+
*
|
|
8
|
+
* Setup:
|
|
9
|
+
* 1. Copy .env.example → .env and fill in your keys
|
|
10
|
+
* 2. Run: npx tsx index.ts
|
|
11
|
+
* 3. Expose with: npx localtunnel --port 3000
|
|
12
|
+
* 4. Register the tunnel URL as your Telegram/WhatsApp webhook
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import 'dotenv/config';
|
|
16
|
+
import { SvaraAgent, createTool } from '@yesvara/svara';
|
|
17
|
+
|
|
18
|
+
// ── Tools ─────────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const orderStatus = createTool({
|
|
21
|
+
name: 'get_order_status',
|
|
22
|
+
description: 'Look up the status of a customer order by order ID',
|
|
23
|
+
parameters: {
|
|
24
|
+
orderId: { type: 'string', description: 'The order ID', required: true },
|
|
25
|
+
},
|
|
26
|
+
async run({ orderId }) {
|
|
27
|
+
// Replace with your real database query
|
|
28
|
+
const mockOrders: Record<string, unknown> = {
|
|
29
|
+
'ORD-001': { status: 'shipped', eta: '2024-12-25', carrier: 'FedEx' },
|
|
30
|
+
'ORD-002': { status: 'processing', eta: '2024-12-27', carrier: null },
|
|
31
|
+
};
|
|
32
|
+
return mockOrders[orderId as string] ?? { error: 'Order not found' };
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ── Agent ─────────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
const agent = new SvaraAgent({
|
|
39
|
+
name: 'Aria Support',
|
|
40
|
+
model: 'gpt-4o-mini',
|
|
41
|
+
knowledge: './policies', // auto-indexed on start()
|
|
42
|
+
|
|
43
|
+
systemPrompt: `You are Aria, the customer support assistant for Acme Store.
|
|
44
|
+
You are helpful, empathetic, and solution-focused.
|
|
45
|
+
You can check order status and answer questions from our knowledge base.
|
|
46
|
+
Always greet customers by name if they provide it.`,
|
|
47
|
+
|
|
48
|
+
memory: { window: 30 },
|
|
49
|
+
tools: [orderStatus],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// ── Channels ──────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
agent
|
|
55
|
+
// Web API — for website chat widget
|
|
56
|
+
.connectChannel('web', {
|
|
57
|
+
port: 3000,
|
|
58
|
+
cors: true,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Telegram Bot
|
|
62
|
+
.connectChannel('telegram', {
|
|
63
|
+
token: process.env.TELEGRAM_BOT_TOKEN!,
|
|
64
|
+
mode: 'polling', // use 'webhook' in production
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// WhatsApp Business
|
|
68
|
+
.connectChannel('whatsapp', {
|
|
69
|
+
token: process.env.WA_ACCESS_TOKEN!,
|
|
70
|
+
phoneId: process.env.WA_PHONE_ID!,
|
|
71
|
+
verifyToken: process.env.WA_VERIFY_TOKEN!,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ── Events ────────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
agent.on('message:received', ({ message, sessionId }: { message: string; sessionId: string }) => {
|
|
77
|
+
console.log(`[${sessionId.slice(0, 8)}] User: ${message.slice(0, 50)}...`);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
agent.on('tool:call', ({ tools }: { tools: string[] }) => {
|
|
81
|
+
console.log(`[Tools] → ${tools.join(', ')}`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
agent.on('channel:ready', ({ channel }: { channel: string }) => {
|
|
85
|
+
console.log(`[Channel] ${channel} is ready`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
await agent.start();
|
|
91
|
+
console.log('\n🚀 Aria Support is live across all channels!\n');
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yesvara/svara",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Build AI agents in 15 lines of code. Multi-channel, RAG-ready, production-grade.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"svara": "./dist/cli/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup src/index.ts src/cli/index.ts --format cjs,esm --dts --clean",
|
|
13
|
+
"dev": "tsup src/index.ts --watch --format cjs",
|
|
14
|
+
"test": "vitest",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"prepublishOnly": "npm run build && npm run typecheck"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./dist/index.mjs",
|
|
21
|
+
"require": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"ai", "agent", "llm", "openai", "anthropic", "rag",
|
|
27
|
+
"chatbot", "whatsapp", "telegram", "framework", "agentic"
|
|
28
|
+
],
|
|
29
|
+
"author": "Yesvara Contributors",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/yogiswara92/svara"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://svarajs.yesvara.com",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"express": "^4.18.2",
|
|
41
|
+
"better-sqlite3": "^9.4.3",
|
|
42
|
+
"openai": "^4.47.1",
|
|
43
|
+
"@anthropic-ai/sdk": "^0.24.3",
|
|
44
|
+
"zod": "^3.23.8",
|
|
45
|
+
"chalk": "^5.3.0",
|
|
46
|
+
"commander": "^12.1.0",
|
|
47
|
+
"dotenv": "^16.4.5",
|
|
48
|
+
"glob": "^10.4.1",
|
|
49
|
+
"pdf-parse": "^1.1.1",
|
|
50
|
+
"mammoth": "^1.7.2",
|
|
51
|
+
"ora": "^8.0.1",
|
|
52
|
+
"inquirer": "^9.3.2"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/express": "^4.17.21",
|
|
56
|
+
"@types/better-sqlite3": "^7.6.10",
|
|
57
|
+
"@types/node": "^20.14.2",
|
|
58
|
+
"@types/pdf-parse": "^1.1.4",
|
|
59
|
+
"tsup": "^8.1.0",
|
|
60
|
+
"typescript": "^5.4.5",
|
|
61
|
+
"vitest": "^1.6.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"openai": ">=4.0.0",
|
|
65
|
+
"@anthropic-ai/sdk": ">=0.20.0"
|
|
66
|
+
},
|
|
67
|
+
"peerDependenciesMeta": {
|
|
68
|
+
"openai": { "optional": true },
|
|
69
|
+
"@anthropic-ai/sdk": { "optional": true }
|
|
70
|
+
},
|
|
71
|
+
"engines": {
|
|
72
|
+
"node": ">=18.0.0"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/app/index.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module SvaraApp
|
|
3
|
+
*
|
|
4
|
+
* The framework entry point. Wraps Express to give you a clean,
|
|
5
|
+
* AI-first HTTP server that works with zero configuration.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { SvaraApp, SvaraAgent } from '@yesvara/svara';
|
|
10
|
+
*
|
|
11
|
+
* const app = new SvaraApp();
|
|
12
|
+
*
|
|
13
|
+
* const agent = new SvaraAgent({
|
|
14
|
+
* name: 'Support Bot',
|
|
15
|
+
* model: 'gpt-4o-mini',
|
|
16
|
+
* knowledge: './docs',
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* app.route('/chat', agent.handler());
|
|
20
|
+
* app.listen(3000);
|
|
21
|
+
* // → Server running at http://localhost:3000
|
|
22
|
+
* // → POST /chat accepts { message, sessionId }
|
|
23
|
+
* // → GET /health returns { status: 'ok' }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example With your own Express app
|
|
27
|
+
* ```ts
|
|
28
|
+
* import express from 'express';
|
|
29
|
+
* const expressApp = express();
|
|
30
|
+
*
|
|
31
|
+
* // Mount as middleware on any path
|
|
32
|
+
* expressApp.post('/api/chat', agent.handler());
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import express, { type Express, type RequestHandler, type Request, type Response } from 'express';
|
|
37
|
+
import { createServer, type Server } from 'http';
|
|
38
|
+
|
|
39
|
+
export interface AppOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Enable CORS. Pass `true` for wildcard (*), or a specific origin string.
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
cors?: boolean | string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Require an API key in the `Authorization: Bearer <key>` header.
|
|
48
|
+
* Useful for protecting your agent endpoint.
|
|
49
|
+
*/
|
|
50
|
+
apiKey?: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Request body size limit. @default '10mb'
|
|
54
|
+
*/
|
|
55
|
+
bodyLimit?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class SvaraApp {
|
|
59
|
+
private readonly express: Express;
|
|
60
|
+
private server: Server | null = null;
|
|
61
|
+
|
|
62
|
+
constructor(options: AppOptions = {}) {
|
|
63
|
+
this.express = express();
|
|
64
|
+
this.setup(options);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Public API ────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Mount an agent (or any Express handler) on a route.
|
|
71
|
+
* Returns `this` for chaining.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* app
|
|
75
|
+
* .route('/chat', supportAgent.handler())
|
|
76
|
+
* .route('/sales', salesAgent.handler());
|
|
77
|
+
*/
|
|
78
|
+
route(path: string, handler: RequestHandler): this {
|
|
79
|
+
this.express.post(path, handler);
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Add Express middleware (logging, auth, rate limiting, etc.)
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* import rateLimit from 'express-rate-limit';
|
|
88
|
+
* app.use(rateLimit({ windowMs: 60_000, max: 100 }));
|
|
89
|
+
*/
|
|
90
|
+
use(middleware: RequestHandler): this {
|
|
91
|
+
this.express.use(middleware);
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Start listening on the given port.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* await app.listen(3000);
|
|
100
|
+
* // → [@yesvara/svara] Listening at http://localhost:3000
|
|
101
|
+
*/
|
|
102
|
+
listen(port = 3000): Promise<void> {
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
this.server = createServer(this.express);
|
|
105
|
+
this.server.listen(port, () => {
|
|
106
|
+
console.log(`[@yesvara/svara] Server running at http://localhost:${port}`);
|
|
107
|
+
resolve();
|
|
108
|
+
});
|
|
109
|
+
this.server.on('error', reject);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Stop the server gracefully.
|
|
115
|
+
*/
|
|
116
|
+
stop(): Promise<void> {
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
if (this.server) {
|
|
119
|
+
this.server.close(() => resolve());
|
|
120
|
+
} else {
|
|
121
|
+
resolve();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Access the underlying Express app for advanced configuration.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* const expressApp = app.express();
|
|
131
|
+
* expressApp.set('trust proxy', 1);
|
|
132
|
+
*/
|
|
133
|
+
getExpressApp(): Express {
|
|
134
|
+
return this.express;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Private Setup ────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
private setup(options: AppOptions): void {
|
|
140
|
+
// Parse JSON bodies
|
|
141
|
+
this.express.use(express.json({ limit: options.bodyLimit ?? '10mb' }));
|
|
142
|
+
|
|
143
|
+
// CORS
|
|
144
|
+
if (options.cors) {
|
|
145
|
+
this.express.use((_req, res, next) => {
|
|
146
|
+
const origin = options.cors === true ? '*' : options.cors as string;
|
|
147
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
148
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
149
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
150
|
+
next();
|
|
151
|
+
});
|
|
152
|
+
this.express.options('*', (_req, res) => res.sendStatus(204));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// API key auth
|
|
156
|
+
if (options.apiKey) {
|
|
157
|
+
this.express.use((req, res, next) => {
|
|
158
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
159
|
+
if (token !== options.apiKey) {
|
|
160
|
+
res.status(401).json({ error: 'Unauthorized', message: 'Invalid API key.' });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
next();
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Health check — always available, no auth
|
|
168
|
+
this.express.get('/health', (_req: Request, res: Response) => {
|
|
169
|
+
res.json({
|
|
170
|
+
status: 'ok',
|
|
171
|
+
framework: '@yesvara/svara',
|
|
172
|
+
timestamp: new Date().toISOString(),
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|