agentlang 0.9.7 → 0.9.8

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.
@@ -31,6 +31,7 @@ import {
31
31
  DefaultModules,
32
32
  encryptPassword,
33
33
  escapeFqName,
34
+ escapeSpecialChars,
34
35
  findAllPrePostTriggerSchema,
35
36
  findMetaSchema,
36
37
  findUqCompositeAttributes,
@@ -1161,7 +1162,16 @@ export class Agent extends Record {
1161
1162
  if (!skip && value !== null && value !== undefined) {
1162
1163
  let v = value;
1163
1164
  const isf = key == 'flows';
1164
- if (isf || key == 'tools') {
1165
+ const isDocs = key == 'documents';
1166
+ if (isDocs) {
1167
+ const raw = isString(v) ? v : `${v}`;
1168
+ const items = raw
1169
+ .split(',')
1170
+ .map((entry: string) => entry.trim())
1171
+ .filter((entry: string) => entry.length > 0)
1172
+ .map((entry: string) => entry.replace(/\\/g, '\\\\').replace(/"/g, '\\"'));
1173
+ v = `[${items.map((entry: string) => `"${entry}"`).join(', ')}]`;
1174
+ } else if (isf || key == 'tools') {
1165
1175
  if (isf || v.indexOf(',') > 0 || v.indexOf('/') > 0) v = `[${v}]`;
1166
1176
  else v = `"${v}"`;
1167
1177
  } else if (isString(v)) {
@@ -1885,6 +1895,42 @@ export class GlossaryEntry extends ModuleEntry {
1885
1895
  }
1886
1896
  }
1887
1897
 
1898
+ export type ModuleDocument = {
1899
+ title: string;
1900
+ url: string;
1901
+ };
1902
+
1903
+ export class DocumentEntry extends ModuleEntry {
1904
+ private doc: ModuleDocument;
1905
+
1906
+ constructor(title: string, moduleName: string, url: string) {
1907
+ super(title, moduleName);
1908
+ this.doc = { title, url };
1909
+ }
1910
+
1911
+ getUrl(): string {
1912
+ return this.doc.url;
1913
+ }
1914
+
1915
+ getTitle(): string {
1916
+ return this.doc.title;
1917
+ }
1918
+
1919
+ override toString(): string {
1920
+ const escapedTitle = escapeSpecialChars(this.doc.title);
1921
+ const escapedUrl = escapeSpecialChars(this.doc.url);
1922
+ return `{${DefaultModuleName}.ai/doc {\n title "${escapedTitle}",\n url "${escapedUrl}"\n}}`;
1923
+ }
1924
+
1925
+ get url(): string {
1926
+ return this.doc.url;
1927
+ }
1928
+
1929
+ set url(value: string) {
1930
+ this.doc.url = value;
1931
+ }
1932
+ }
1933
+
1888
1934
  function statementLabel(stmt: Statement | undefined): string {
1889
1935
  if (stmt === undefined) return '';
1890
1936
  let lbl: string | undefined = undefined;
@@ -2472,6 +2518,48 @@ export class Module {
2472
2518
  return this;
2473
2519
  }
2474
2520
 
2521
+ addDocument(title: string, url: string): Module {
2522
+ if (this.hasEntry(title)) {
2523
+ const existing = this.getEntry(title);
2524
+ if (existing instanceof DocumentEntry) {
2525
+ const doc = existing as DocumentEntry;
2526
+ doc.url = url;
2527
+ return this;
2528
+ }
2529
+ }
2530
+
2531
+ const docEntry = new DocumentEntry(title, this.name, url);
2532
+ this.addEntry(docEntry);
2533
+ return this;
2534
+ }
2535
+
2536
+ getDocument(title: string): string | undefined {
2537
+ if (this.hasEntry(title)) {
2538
+ const entry = this.getEntry(title);
2539
+ if (entry instanceof DocumentEntry) {
2540
+ return (entry as DocumentEntry).url;
2541
+ }
2542
+ }
2543
+ return undefined;
2544
+ }
2545
+
2546
+ getAllDocuments(): DocumentEntry[] {
2547
+ return this.entries.filter((e: ModuleEntry) => {
2548
+ return e instanceof DocumentEntry;
2549
+ }) as DocumentEntry[];
2550
+ }
2551
+
2552
+ removeDocument(title: string): Module {
2553
+ for (let i = 0; i < this.entries.length; ++i) {
2554
+ const entry = this.entries[i];
2555
+ if (entry.name === title && entry instanceof DocumentEntry) {
2556
+ this.entries.splice(i, 1);
2557
+ break;
2558
+ }
2559
+ }
2560
+ return this;
2561
+ }
2562
+
2475
2563
  addRetry(retry: Retry): Module {
2476
2564
  this.addEntry(retry);
2477
2565
  return this;
@@ -2734,6 +2822,17 @@ export class Module {
2734
2822
 
2735
2823
  let GlobalRetries: Array<Retry> | undefined = undefined;
2736
2824
 
2825
+ const DocumentAliasMap: Map<string, string> = new Map();
2826
+
2827
+ export function registerDocumentAlias(name: string, title: string): void {
2828
+ if (!name || !title) return;
2829
+ DocumentAliasMap.set(name, title);
2830
+ }
2831
+
2832
+ export function resolveDocumentAliases(names: string[]): string[] {
2833
+ return names.map((name: string) => DocumentAliasMap.get(name) ?? name);
2834
+ }
2835
+
2737
2836
  export function addGlobalRetry(r: Retry): Retry {
2738
2837
  if (GlobalRetries === undefined) {
2739
2838
  GlobalRetries = new Array<Retry>();
@@ -31,6 +31,7 @@ import {
31
31
  newInstanceAttributes,
32
32
  Record,
33
33
  Retry,
34
+ resolveDocumentAliases,
34
35
  } from '../module.js';
35
36
  import { provider } from '../agents/registry.js';
36
37
  import {
@@ -73,6 +74,27 @@ export const AgentLearnerType = 'learner';
73
74
 
74
75
  const AgentEvalType = 'eval';
75
76
 
77
+ function buildEmbeddingConfig(): object {
78
+ const config: any = {
79
+ provider: process.env.AGENTLANG_EMBEDDING_PROVIDER || 'openai',
80
+ model: process.env.AGENTLANG_EMBEDDING_MODEL || 'text-embedding-3-small',
81
+ chunkSize: 1000,
82
+ chunkOverlap: 200,
83
+ };
84
+
85
+ if (process.env.AGENTLANG_EMBEDDING_CHUNKSIZE) {
86
+ config.chunkSize = parseInt(process.env.AGENTLANG_EMBEDDING_CHUNKSIZE, 1000);
87
+ }
88
+
89
+ if (process.env.AGENTLANG_EMBEDDING_CHUNKOVERLAP) {
90
+ config.chunkOverlap = parseInt(process.env.AGENTLANG_EMBEDDING_CHUNKOVERLAP, 200);
91
+ }
92
+
93
+ return config;
94
+ }
95
+
96
+ const embeddingConfig = JSON.stringify(buildEmbeddingConfig());
97
+
76
98
  export default `module ${CoreAIModuleName}
77
99
 
78
100
  import "./modules/ai.js" @as ai
@@ -116,7 +138,7 @@ workflow saveAgentChatSession {
116
138
  entity Document {
117
139
  title String @id,
118
140
  content String,
119
- @meta {"fullTextSearch": "*"}
141
+ @meta {"fullTextSearch": "*", "embeddingConfig": ${embeddingConfig}}
120
142
  }
121
143
 
122
144
  event doc {
@@ -981,6 +1003,7 @@ Only return a pure JSON object with no extra text, annotations etc.`;
981
1003
  if (this.documents && this.documents.length > 0) {
982
1004
  try {
983
1005
  const docNames = this.documents.split(',').map(d => d.trim());
1006
+ const docTitles = resolveDocumentAliases(docNames);
984
1007
 
985
1008
  const searchQuery = message;
986
1009
 
@@ -994,7 +1017,7 @@ Only return a pure JSON object with no extra text, annotations etc.`;
994
1017
  const docs: Instance[] = [];
995
1018
  for (const doc of semanticResult) {
996
1019
  const docTitle = doc.lookup ? doc.lookup('title') : doc.title;
997
- if (AgentInstance.docTitlesMatch(docTitle, docNames)) {
1020
+ if (AgentInstance.docTitlesMatch(docTitle, docTitles)) {
998
1021
  docs.push(
999
1022
  doc instanceof Instance
1000
1023
  ? doc
@@ -1026,7 +1049,7 @@ Only return a pure JSON object with no extra text, annotations etc.`;
1026
1049
  const v: any = result[i];
1027
1050
  const docTitle: string | undefined = AgentInstance.getDocumentTitle(v);
1028
1051
 
1029
- if (docTitle && docNames.includes(docTitle)) {
1052
+ if (docTitle && docTitles.includes(docTitle)) {
1030
1053
  if (v instanceof Instance) {
1031
1054
  docs.push(v);
1032
1055
  }
@@ -49,6 +49,67 @@ import { AppConfig } from '../../state.js';
49
49
 
50
50
  export let defaultDataSource: DataSource | undefined;
51
51
 
52
+ // Detect browser environment
53
+ function isBrowser(): boolean {
54
+ // window for DOM pages, self+importScripts for web workers
55
+ return (
56
+ (typeof window !== 'undefined' && typeof (window as any).document !== 'undefined') ||
57
+ (typeof self !== 'undefined' && typeof (self as any).importScripts === 'function')
58
+ );
59
+ }
60
+
61
+ // SQLite WASM with built-in sqlite-vec for browsers
62
+ // Loaded from CDN - this has sqlite-vec statically compiled in
63
+ let sqliteVecWasmModule: any = null;
64
+
65
+ async function loadSqliteVecWasm(): Promise<any> {
66
+ if (sqliteVecWasmModule) {
67
+ return sqliteVecWasmModule;
68
+ }
69
+
70
+ try {
71
+ // Use dynamic import with string to prevent bundlers from analyzing
72
+ const cdnUrl = 'https://cdn.jsdelivr.net/npm/sqlite-vec-wasm-demo@latest/sqlite3.mjs';
73
+ const module = await import(/* @vite-ignore */ cdnUrl);
74
+ sqliteVecWasmModule = await module.default();
75
+ return sqliteVecWasmModule;
76
+ } catch (err) {
77
+ logger.warn('Failed to load sqlite-vec WASM:', err);
78
+ return null;
79
+ }
80
+ }
81
+
82
+ // Helper to load sqlite-vec based on environment
83
+ async function loadSqliteVec(): Promise<any> {
84
+ // In browser, use WASM version with built-in sqlite-vec
85
+ // In Node.js, use the npm package
86
+ if (isBrowser()) {
87
+ const wasmModule = await loadSqliteVecWasm();
88
+ if (wasmModule) {
89
+ // WASM version has sqlite-vec built-in, no need to call load()
90
+ // Return a compatible interface
91
+ return {
92
+ load: () => {
93
+ // No-op: sqlite-vec is already loaded in WASM version
94
+ logger.info('sqlite-vec WASM loaded (built-in)');
95
+ },
96
+ // Expose the WASM module for direct use if needed
97
+ _wasmModule: wasmModule,
98
+ };
99
+ }
100
+ return null;
101
+ }
102
+
103
+ // Node.js: use npm package
104
+ try {
105
+ // Use variable to prevent bundlers from statically analyzing this import
106
+ const moduleName = 'sqlite-vec';
107
+ return await import(/* @vite-ignore */ moduleName);
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+
52
113
  export class DbContext {
53
114
  txnId: string | undefined;
54
115
  authInfo: ResolverAuthInfo;
@@ -245,11 +306,11 @@ function makeSqliteDataSource(
245
306
  ds.initialize = async () => {
246
307
  const res = await originalInit();
247
308
  try {
248
- const { load } = await import('sqlite-vec');
309
+ const sqliteVec = await loadSqliteVec();
249
310
  const driver = ds.driver as any;
250
311
  const db = driver.databaseConnection || driver.nativeDatabase;
251
- if (db) {
252
- load(db);
312
+ if (db && sqliteVec?.load) {
313
+ sqliteVec.load(db);
253
314
  logger.info('sqlite-vec extension loaded successfully');
254
315
  }
255
316
  } catch (err: any) {
@@ -303,14 +364,6 @@ async function maybeHandleMigrations(dataSource: DataSource) {
303
364
  }
304
365
  }
305
366
 
306
- function isBrowser(): boolean {
307
- // window for DOM pages, self+importScripts for web workers
308
- return (
309
- (typeof window !== 'undefined' && typeof (window as any).document !== 'undefined') ||
310
- (typeof self !== 'undefined' && typeof (self as any).importScripts === 'function')
311
- );
312
- }
313
-
314
367
  function defaultLocateFile(file: string): string {
315
368
  // Out-of-the-box: use the official CDN in browsers.
316
369
  if (isBrowser()) {
@@ -395,12 +448,8 @@ export async function isVectorStoreSupported(): Promise<boolean> {
395
448
  const dbType = getDbType(AppConfig?.store);
396
449
  if (dbType === 'postgres') return true;
397
450
  if (dbType === 'sqlite') {
398
- try {
399
- const sqliteVecModule = await import('sqlite-vec');
400
- return !!sqliteVecModule;
401
- } catch {
402
- return false;
403
- }
451
+ const sqliteVecModule = await loadSqliteVec();
452
+ return !!sqliteVecModule;
404
453
  }
405
454
  return false;
406
455
  }