@snap-agent/core 0.1.0 → 0.1.3

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/dist/index.mjs CHANGED
@@ -2,10 +2,10 @@ import {
2
2
  MemoryStorage,
3
3
  MongoDBStorage,
4
4
  UpstashStorage
5
- } from "./chunk-Y5TTFQWC.mjs";
5
+ } from "./chunk-FS7G3ID4.mjs";
6
6
 
7
7
  // src/core/Agent.ts
8
- import { generateText, streamText } from "ai";
8
+ import { generateText, streamText, Output } from "ai";
9
9
 
10
10
  // src/core/PluginManager.ts
11
11
  var PluginManager = class {
@@ -219,33 +219,88 @@ var Agent = class _Agent {
219
219
  }
220
220
  /**
221
221
  * Create a new agent
222
+ *
223
+ * If plugins are provided, their configurations will be extracted (if they implement getConfig())
224
+ * and stored in the database for later reinstantiation.
222
225
  */
223
226
  static async create(config, storage, providerFactory) {
224
- const agentId = await storage.createAgent(config);
227
+ const pluginConfigs = config.pluginConfigs || [];
228
+ if (config.plugins && config.plugins.length > 0) {
229
+ for (const plugin of config.plugins) {
230
+ if ("getConfig" in plugin && typeof plugin.getConfig === "function") {
231
+ pluginConfigs.push({
232
+ type: plugin.type,
233
+ name: plugin.name,
234
+ config: plugin.getConfig(),
235
+ priority: plugin.priority,
236
+ enabled: true
237
+ });
238
+ }
239
+ }
240
+ }
241
+ const configWithPluginConfigs = {
242
+ ...config,
243
+ pluginConfigs
244
+ };
245
+ const agentId = await storage.createAgent(configWithPluginConfigs);
225
246
  const data = await storage.getAgent(agentId);
226
247
  if (!data) {
227
248
  throw new AgentNotFoundError(agentId);
228
249
  }
250
+ data.plugins = config.plugins || [];
229
251
  return new _Agent(data, storage, providerFactory);
230
252
  }
231
253
  /**
232
254
  * Load an existing agent by ID
233
- */
234
- static async load(agentId, storage, providerFactory) {
255
+ *
256
+ * Plugins can be attached in three ways (in order of priority):
257
+ * 1. Direct plugins array - runtime plugin instances passed directly
258
+ * 2. Plugin registry - reinstantiate from stored configs using registered factories
259
+ * 3. No plugins - agent loads without plugin functionality
260
+ *
261
+ * @param agentId - The agent ID to load
262
+ * @param storage - Storage adapter
263
+ * @param providerFactory - Provider factory
264
+ * @param options - Either:
265
+ * - Plugin[] array (legacy, for backwards compatibility)
266
+ * - Options object with plugins and/or registry
267
+ */
268
+ static async load(agentId, storage, providerFactory, options) {
235
269
  const data = await storage.getAgent(agentId);
236
270
  if (!data) {
237
271
  return null;
238
272
  }
273
+ if (Array.isArray(options)) {
274
+ data.plugins = options;
275
+ return new _Agent(data, storage, providerFactory);
276
+ }
277
+ if (options?.plugins && options.plugins.length > 0) {
278
+ data.plugins = options.plugins;
279
+ } else if (options?.registry && data.pluginConfigs && data.pluginConfigs.length > 0) {
280
+ try {
281
+ data.plugins = await options.registry.instantiateAll(data.pluginConfigs);
282
+ } catch (error) {
283
+ console.error("Failed to reinstantiate plugins from stored configs:", error);
284
+ throw error;
285
+ }
286
+ } else {
287
+ data.plugins = [];
288
+ }
239
289
  return new _Agent(data, storage, providerFactory);
240
290
  }
241
291
  /**
242
292
  * Update agent properties
243
293
  */
244
294
  async update(updates) {
295
+ const currentPlugins = this.data.plugins || [];
245
296
  await this.storage.updateAgent(this.data.id, updates);
246
297
  const updatedData = await this.storage.getAgent(this.data.id);
247
298
  if (updatedData) {
299
+ updatedData.plugins = updates.plugins || currentPlugins;
248
300
  this.data = updatedData;
301
+ if (updates.plugins) {
302
+ this.pluginManager = new PluginManager(updatedData.plugins);
303
+ }
249
304
  }
250
305
  }
251
306
  /**
@@ -297,11 +352,37 @@ var Agent = class _Agent {
297
352
  ragMetadata = allMetadata;
298
353
  }
299
354
  const model = await this.providerFactory.getModel(this.data.provider, this.data.model);
300
- const { text } = await generateText({
301
- model,
302
- messages: beforeResult.messages,
303
- system: systemPrompt
304
- });
355
+ let text;
356
+ let parsed;
357
+ if (options?.output?.mode === "object") {
358
+ const result = await generateText({
359
+ model,
360
+ messages: beforeResult.messages,
361
+ system: systemPrompt,
362
+ experimental_output: Output.object({ schema: options.output.schema })
363
+ });
364
+ text = JSON.stringify(result.experimental_output);
365
+ parsed = result.experimental_output;
366
+ } else if (options?.output?.mode === "json") {
367
+ const jsonSystemPrompt = systemPrompt + "\n\n---\nOUTPUT FORMAT: You MUST respond with valid JSON only. No markdown code blocks, no explanations, no additional text - just raw JSON that can be parsed directly.";
368
+ const result = await generateText({
369
+ model,
370
+ messages: beforeResult.messages,
371
+ system: jsonSystemPrompt
372
+ });
373
+ text = result.text;
374
+ try {
375
+ parsed = JSON.parse(text);
376
+ } catch {
377
+ }
378
+ } else {
379
+ const result = await generateText({
380
+ model,
381
+ messages: beforeResult.messages,
382
+ system: systemPrompt
383
+ });
384
+ text = result.text;
385
+ }
305
386
  const afterResult = await this.pluginManager.executeAfterResponse(text, {
306
387
  agentId: this.data.id,
307
388
  threadId: options?.threadId,
@@ -317,6 +398,7 @@ var Agent = class _Agent {
317
398
  });
318
399
  return {
319
400
  text: afterResult.response,
401
+ ...parsed !== void 0 && { parsed },
320
402
  metadata: {
321
403
  ...afterResult.metadata,
322
404
  ragMetadata,
@@ -1058,6 +1140,7 @@ var AgentClient = class {
1058
1140
  this.storage = config.storage;
1059
1141
  this.providers = config.providers;
1060
1142
  this.providerFactory = new ProviderFactory(config.providers);
1143
+ this.pluginRegistry = config.pluginRegistry;
1061
1144
  }
1062
1145
  validateConfig(config) {
1063
1146
  if (!config.storage) {
@@ -1100,9 +1183,28 @@ var AgentClient = class {
1100
1183
  }
1101
1184
  /**
1102
1185
  * Get an agent by ID
1103
- */
1104
- async getAgent(agentId) {
1105
- const agent = await Agent.load(agentId, this.storage, this.providerFactory);
1186
+ *
1187
+ * Plugin loading priority:
1188
+ * 1. Direct plugins array passed to this method
1189
+ * 2. Plugin registry (if configured) - reinstantiates from stored configs
1190
+ * 3. No plugins
1191
+ *
1192
+ * @param agentId - The agent ID to load
1193
+ * @param options - Either Plugin[] for backwards compatibility, or options object
1194
+ */
1195
+ async getAgent(agentId, options) {
1196
+ if (Array.isArray(options)) {
1197
+ const agent2 = await Agent.load(agentId, this.storage, this.providerFactory, options);
1198
+ if (!agent2) {
1199
+ throw new AgentNotFoundError(agentId);
1200
+ }
1201
+ return agent2;
1202
+ }
1203
+ const registry = options?.registry || this.pluginRegistry;
1204
+ const agent = await Agent.load(agentId, this.storage, this.providerFactory, {
1205
+ plugins: options?.plugins,
1206
+ registry
1207
+ });
1106
1208
  if (!agent) {
1107
1209
  throw new AgentNotFoundError(agentId);
1108
1210
  }
@@ -1260,6 +1362,149 @@ Return only the title without additional explanations.`
1260
1362
  }
1261
1363
  };
1262
1364
 
1365
+ // src/core/PluginRegistry.ts
1366
+ function resolveEnvVars(config) {
1367
+ const resolved = {};
1368
+ for (const [key, value] of Object.entries(config)) {
1369
+ if (typeof value === "string") {
1370
+ const envMatch = value.match(/^\$\{([^}:]+)(?::([^}]*))?\}$/);
1371
+ if (envMatch) {
1372
+ const [, envVar, defaultValue] = envMatch;
1373
+ const envValue = process.env[envVar];
1374
+ if (envValue !== void 0) {
1375
+ resolved[key] = envValue;
1376
+ } else if (defaultValue !== void 0) {
1377
+ resolved[key] = defaultValue;
1378
+ } else {
1379
+ throw new Error(
1380
+ `Environment variable ${envVar} is required for plugin config but not set`
1381
+ );
1382
+ }
1383
+ } else {
1384
+ resolved[key] = value;
1385
+ }
1386
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
1387
+ resolved[key] = resolveEnvVars(value);
1388
+ } else {
1389
+ resolved[key] = value;
1390
+ }
1391
+ }
1392
+ return resolved;
1393
+ }
1394
+ var PluginRegistry = class {
1395
+ constructor() {
1396
+ this.registrations = /* @__PURE__ */ new Map();
1397
+ }
1398
+ /**
1399
+ * Register a plugin factory
1400
+ *
1401
+ * @param name - Unique plugin identifier (e.g., "@snap-agent/rag-ecommerce")
1402
+ * @param factory - Function that creates plugin instance from config
1403
+ * @param defaultConfig - Optional default configuration values
1404
+ */
1405
+ register(name, factory, defaultConfig) {
1406
+ if (this.registrations.has(name)) {
1407
+ console.warn(`Plugin "${name}" is already registered. Overwriting.`);
1408
+ }
1409
+ this.registrations.set(name, { factory, defaultConfig });
1410
+ }
1411
+ /**
1412
+ * Unregister a plugin factory
1413
+ */
1414
+ unregister(name) {
1415
+ return this.registrations.delete(name);
1416
+ }
1417
+ /**
1418
+ * Check if a plugin is registered
1419
+ */
1420
+ isRegistered(name) {
1421
+ return this.registrations.has(name);
1422
+ }
1423
+ /**
1424
+ * Get all registered plugin names
1425
+ */
1426
+ getRegisteredPlugins() {
1427
+ return Array.from(this.registrations.keys());
1428
+ }
1429
+ /**
1430
+ * Instantiate a plugin from stored configuration
1431
+ *
1432
+ * @param storedConfig - Serialized plugin configuration from database
1433
+ * @returns Plugin instance
1434
+ * @throws Error if plugin is not registered
1435
+ */
1436
+ async instantiate(storedConfig) {
1437
+ const registration = this.registrations.get(storedConfig.name);
1438
+ if (!registration) {
1439
+ throw new Error(
1440
+ `Plugin "${storedConfig.name}" is not registered. Available plugins: ${this.getRegisteredPlugins().join(", ") || "none"}. Make sure to register the plugin before loading the agent.`
1441
+ );
1442
+ }
1443
+ const mergedConfig = {
1444
+ ...registration.defaultConfig,
1445
+ ...storedConfig.config
1446
+ };
1447
+ const resolvedConfig = resolveEnvVars(mergedConfig);
1448
+ const plugin = await registration.factory(resolvedConfig);
1449
+ if (storedConfig.priority !== void 0) {
1450
+ plugin.priority = storedConfig.priority;
1451
+ }
1452
+ return plugin;
1453
+ }
1454
+ /**
1455
+ * Instantiate multiple plugins from stored configurations
1456
+ *
1457
+ * @param storedConfigs - Array of serialized plugin configurations
1458
+ * @returns Array of plugin instances (skips disabled plugins)
1459
+ */
1460
+ async instantiateAll(storedConfigs) {
1461
+ const plugins = [];
1462
+ for (const config of storedConfigs) {
1463
+ if (config.enabled === false) {
1464
+ continue;
1465
+ }
1466
+ try {
1467
+ const plugin = await this.instantiate(config);
1468
+ plugins.push(plugin);
1469
+ } catch (error) {
1470
+ console.error(`Failed to instantiate plugin "${config.name}":`, error);
1471
+ throw error;
1472
+ }
1473
+ }
1474
+ return plugins;
1475
+ }
1476
+ /**
1477
+ * Extract serializable configuration from a plugin instance
1478
+ * Requires the plugin to implement getConfig() method
1479
+ *
1480
+ * @param plugin - Plugin instance
1481
+ * @returns Stored plugin configuration
1482
+ */
1483
+ extractConfig(plugin) {
1484
+ const config = plugin.getConfig?.() ?? {};
1485
+ return {
1486
+ type: plugin.type,
1487
+ name: plugin.name,
1488
+ config,
1489
+ priority: plugin.priority,
1490
+ enabled: true
1491
+ };
1492
+ }
1493
+ /**
1494
+ * Extract configurations from multiple plugins
1495
+ */
1496
+ extractAllConfigs(plugins) {
1497
+ return plugins.map((plugin) => this.extractConfig(plugin));
1498
+ }
1499
+ };
1500
+ var pluginRegistry = new PluginRegistry();
1501
+ function envRef(envVarName, defaultValue) {
1502
+ if (defaultValue !== void 0) {
1503
+ return `\${${envVarName}:${defaultValue}}`;
1504
+ }
1505
+ return `\${${envVarName}}`;
1506
+ }
1507
+
1263
1508
  // src/index.ts
1264
1509
  function createClient(config) {
1265
1510
  return new AgentClient(config);
@@ -1275,10 +1520,14 @@ export {
1275
1520
  Models,
1276
1521
  MongoDBStorage,
1277
1522
  PluginManager,
1523
+ PluginRegistry,
1278
1524
  ProviderFactory,
1279
1525
  ProviderNotFoundError,
1280
1526
  Thread,
1281
1527
  ThreadNotFoundError,
1282
1528
  UpstashStorage,
1283
- createClient
1529
+ createClient,
1530
+ envRef,
1531
+ pluginRegistry,
1532
+ resolveEnvVars
1284
1533
  };
@@ -1 +1 @@
1
- export { L as MemoryStorage, K as MongoDBStorage, O as MongoDBStorageConfig, N as UpstashStorage, Q as UpstashStorageConfig } from '../index-CDsqnM8L.mjs';
1
+ export { W as MemoryStorage, V as MongoDBStorage, Y as MongoDBStorageConfig, X as UpstashStorage, Z as UpstashStorageConfig } from '../index-m2vDW79n.mjs';
@@ -1 +1 @@
1
- export { L as MemoryStorage, K as MongoDBStorage, O as MongoDBStorageConfig, N as UpstashStorage, Q as UpstashStorageConfig } from '../index-CDsqnM8L.js';
1
+ export { W as MemoryStorage, V as MongoDBStorage, Y as MongoDBStorageConfig, X as UpstashStorage, Z as UpstashStorageConfig } from '../index-m2vDW79n.js';
@@ -79,7 +79,8 @@ var MongoDBStorage = class {
79
79
  createdAt: /* @__PURE__ */ new Date(),
80
80
  updatedAt: /* @__PURE__ */ new Date(),
81
81
  files: [],
82
- metadata: config.metadata || {}
82
+ metadata: config.metadata || {},
83
+ pluginConfigs: config.pluginConfigs || []
83
84
  };
84
85
  const result = await collection.insertOne(doc);
85
86
  return result.insertedId.toString();
@@ -262,7 +263,8 @@ var MongoDBStorage = class {
262
263
  createdAt: doc.createdAt,
263
264
  updatedAt: doc.updatedAt,
264
265
  files: doc.files,
265
- metadata: doc.metadata
266
+ metadata: doc.metadata,
267
+ pluginConfigs: doc.pluginConfigs
266
268
  };
267
269
  }
268
270
  threadDocToData(doc) {
@@ -2,7 +2,7 @@ import {
2
2
  MemoryStorage,
3
3
  MongoDBStorage,
4
4
  UpstashStorage
5
- } from "../chunk-Y5TTFQWC.mjs";
5
+ } from "../chunk-FS7G3ID4.mjs";
6
6
  export {
7
7
  MemoryStorage,
8
8
  MongoDBStorage,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@snap-agent/core",
3
- "version": "0.1.0",
4
- "description": "SnapAgent - The lightweight, snap-in AI agent SDK. Multi-provider support (OpenAI, Anthropic, Google). Edge-runtime compatible.",
3
+ "version": "0.1.3",
4
+ "description": "SnapAgent - The lightweight, snap-in AI agent SDK. Multi-provider support. Edge-runtime compatible.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
@@ -63,8 +63,8 @@
63
63
  "author": "ViloTech",
64
64
  "license": "MIT",
65
65
  "peerDependencies": {
66
- "@ai-sdk/openai": "^2.0.0",
67
- "ai": "^5.0.0",
66
+ "@ai-sdk/openai": "^2.0.0 || ^3.0.0",
67
+ "ai": "^5.0.0 || ^6.0.0",
68
68
  "mongodb": "^6.0.0 || ^7.0.0"
69
69
  },
70
70
  "peerDependenciesMeta": {
@@ -82,13 +82,14 @@
82
82
  }
83
83
  },
84
84
  "devDependencies": {
85
- "@ai-sdk/anthropic": "^2.0.0",
86
- "@ai-sdk/google": "^2.0.0",
85
+ "@ai-sdk/anthropic": "^2.0.0 || ^3.0.0",
86
+ "@ai-sdk/google": "^2.0.0 || ^3.0.0",
87
87
  "@types/node": "^24.0.0",
88
88
  "dotenv": "^17.2.3",
89
89
  "eslint": "^8.0.0",
90
90
  "mongodb": "^7.0.0",
91
91
  "tsup": "^8.0.0",
92
+ "tsx": "^4.21.0",
92
93
  "typescript": "^5.8.0",
93
94
  "vitest": "^1.0.0"
94
95
  },