contextual-agent-sdk
Version:
SDK for building AI agents with seamless voice-text context switching
397 lines • 15.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.KnowledgeBaseProvider = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
class KnowledgeBaseProvider {
id;
name;
source = 'knowledge_base';
priority;
enabled;
config;
documentCache = new Map();
constructor(config) {
this.config = config;
this.id = config.id || 'knowledge_base';
this.name = config.name || 'Knowledge Base Provider';
this.priority = config.priority || 80;
this.enabled = config.enabled ?? true;
if (this.config.options?.autoDiscoverDocs?.enabled) {
this.autoDiscoverDocuments();
}
}
async getContext(params) {
try {
const results = [];
if (this.config.options?.customSearch && params.query) {
const customResults = await this.config.options.customSearch(params.query);
results.push(...customResults);
}
if (this.config.options?.sources) {
for (const source of this.config.options.sources) {
const sourceResults = await this.searchDocumentSource(source, params.query);
results.push(...sourceResults);
}
}
if (results.length === 0) {
return null;
}
return this.formatResults(results);
}
catch (error) {
console.error('Knowledge base search error:', error);
return null;
}
}
formatContext(context) {
if (typeof context.content === 'string') {
return `Documentation:\n${context.content}`;
}
if (Array.isArray(context.content)) {
return `Documentation:\n${context.content.map((item, i) => {
const title = item.title || item.filename || `Document ${i + 1}`;
const content = item.content || item.text || JSON.stringify(item);
const source = item.source ? ` (${item.source})` : '';
return `\n## ${title}${source}\n${content}`;
}).join('\n')}`;
}
return JSON.stringify(context.content, null, 2);
}
async searchDocumentSource(source, query) {
const results = [];
try {
switch (source.type) {
case 'file':
const fileResult = await this.loadFile(source.path, source);
if (fileResult && this.matchesQuery(fileResult.content, query)) {
results.push(fileResult);
}
break;
case 'directory':
const dirResults = await this.loadDirectory(source.path, source);
results.push(...dirResults.filter(result => this.matchesQuery(result.content, query)));
break;
case 'url':
const urlResult = await this.loadUrl(source.path, source);
if (urlResult && this.matchesQuery(urlResult.content, query)) {
results.push(urlResult);
}
break;
case 'custom':
break;
}
}
catch (error) {
console.error(`Error loading source ${source.path}:`, error);
}
return results;
}
async loadFile(filePath, source) {
try {
const cached = this.getCachedDocument(filePath);
if (cached) {
return {
...cached,
source: filePath,
type: 'file',
tags: source.tags || [],
weight: source.weight || 1
};
}
const stats = fs.statSync(filePath);
const content = fs.readFileSync(filePath, { encoding: source.encoding || 'utf8' });
const parsed = this.parseDocument(content, source.format || this.detectFormat(filePath));
this.cacheDocument(filePath, parsed.content, stats.mtimeMs);
return {
title: parsed.title || path.basename(filePath),
content: parsed.content,
filename: path.basename(filePath),
source: filePath,
type: 'file',
tags: source.tags || [],
weight: source.weight || 1,
lastModified: stats.mtimeMs
};
}
catch (error) {
console.error(`Error loading file ${filePath}:`, error);
return null;
}
}
async loadDirectory(dirPath, source) {
const results = [];
try {
const files = fs.readdirSync(dirPath);
for (const file of files) {
const fullPath = path.join(dirPath, file);
const stats = fs.statSync(fullPath);
if (stats.isFile() && this.isDocumentFile(file)) {
const fileResult = await this.loadFile(fullPath, source);
if (fileResult) {
results.push(fileResult);
}
}
}
}
catch (error) {
console.error(`Error loading directory ${dirPath}:`, error);
}
return results;
}
async loadUrl(url, source) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const content = await response.text();
const parsed = this.parseDocument(content, source.format || this.detectFormat(url));
return {
title: parsed.title || url,
content: parsed.content,
source: url,
type: 'url',
tags: source.tags || [],
weight: source.weight || 1
};
}
catch (error) {
console.error(`Error loading URL ${url}:`, error);
return null;
}
}
parseDocument(content, format) {
switch (format.toLowerCase()) {
case 'markdown':
case 'md':
return this.parseMarkdown(content);
case 'json':
return this.parseJson(content);
case 'text':
case 'txt':
default:
return { content };
}
}
parseMarkdown(content) {
const titleMatch = content.match(/^#\s+(.+)$/m);
const title = titleMatch ? titleMatch[1].trim() : undefined;
const cleanContent = content
.replace(/^#{1,6}\s+/gm, '')
.replace(/\*\*(.*?)\*\*/g, '$1')
.replace(/\*(.*?)\*/g, '$1')
.replace(/`(.*?)`/g, '$1')
.replace(/```[\s\S]*?```/g, '[CODE BLOCK]')
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
.trim();
return { title, content: cleanContent };
}
parseJson(content) {
try {
const data = JSON.parse(content);
if (data.title && data.content) {
return { title: data.title, content: data.content };
}
if (data.sections) {
const title = data.title || data.name;
const content = data.sections.map((section) => `${section.title || section.name}: ${section.content || section.description}`).join('\n\n');
return { title, content };
}
return { content: JSON.stringify(data, null, 2) };
}
catch (error) {
return { content };
}
}
detectFormat(filePath) {
const ext = path.extname(filePath).toLowerCase();
const formatMap = {
'.md': 'markdown',
'.markdown': 'markdown',
'.txt': 'text',
'.json': 'json',
'.readme': 'markdown'
};
return formatMap[ext] || 'auto';
}
isDocumentFile(filename) {
const docExtensions = ['.md', '.txt', '.json', '.markdown'];
const docNames = ['readme', 'changelog', 'license', 'contributing', 'docs', 'documentation'];
const ext = path.extname(filename).toLowerCase();
const name = path.basename(filename, ext).toLowerCase();
return docExtensions.includes(ext) || docNames.includes(name);
}
autoDiscoverDocuments() {
const autoConfig = this.config.options?.autoDiscoverDocs;
if (!autoConfig?.enabled)
return;
const rootPath = autoConfig.rootPath || process.cwd();
const patterns = autoConfig.patterns || [
'README.md', 'README.txt', 'README',
'CHANGELOG.md', 'CHANGELOG.txt',
'LICENSE', 'LICENSE.md', 'LICENSE.txt',
'CONTRIBUTING.md', 'CONTRIBUTING.txt',
'docs/**/*.md', 'documentation/**/*.md',
'*.md'
];
const discoveredSources = [];
patterns.forEach(pattern => {
try {
if (!pattern.includes('*')) {
const filePath = path.join(rootPath, pattern);
if (fs.existsSync(filePath)) {
discoveredSources.push({
type: 'file',
path: filePath,
format: 'auto',
weight: this.getDocumentWeight(pattern),
tags: ['auto-discovered', this.getDocumentType(pattern)]
});
}
}
else {
const dirPath = pattern.includes('/') ? path.dirname(pattern) : rootPath;
if (fs.existsSync(path.join(rootPath, dirPath))) {
discoveredSources.push({
type: 'directory',
path: path.join(rootPath, dirPath),
format: 'auto',
weight: 0.7,
tags: ['auto-discovered', 'directory']
});
}
}
}
catch (error) {
console.warn(`Could not auto-discover pattern ${pattern}:`, error);
}
});
if (!this.config.options) {
this.config.options = {};
}
if (!this.config.options.sources) {
this.config.options.sources = [];
}
this.config.options.sources.push(...discoveredSources);
console.log(`Auto-discovered ${discoveredSources.length} documentation sources`);
}
getDocumentWeight(filename) {
const name = filename.toLowerCase();
if (name.includes('readme'))
return 1.0;
if (name.includes('docs') || name.includes('documentation'))
return 0.9;
if (name.includes('changelog'))
return 0.7;
if (name.includes('contributing'))
return 0.6;
if (name.includes('license'))
return 0.5;
return 0.8;
}
getDocumentType(filename) {
const name = filename.toLowerCase();
if (name.includes('readme'))
return 'readme';
if (name.includes('changelog'))
return 'changelog';
if (name.includes('license'))
return 'license';
if (name.includes('contributing'))
return 'contributing';
if (name.includes('docs'))
return 'documentation';
return 'document';
}
matchesQuery(content, query) {
if (!query)
return true;
const searchType = this.config.options?.searchType || 'fuzzy';
const lowerContent = content.toLowerCase();
const lowerQuery = query.toLowerCase();
switch (searchType) {
case 'exact':
return lowerContent.includes(lowerQuery);
case 'fuzzy':
const queryWords = lowerQuery.split(' ').filter(w => w.length > 2);
const matchCount = queryWords.filter(word => lowerContent.includes(word)).length;
return matchCount >= Math.ceil(queryWords.length * 0.6);
case 'semantic':
return this.matchesQuery(content, query);
default:
return true;
}
}
getCachedDocument(filePath) {
const cached = this.documentCache.get(filePath);
if (!cached)
return null;
try {
const stats = fs.statSync(filePath);
if (stats.mtimeMs > cached.lastModified) {
this.documentCache.delete(filePath);
return null;
}
return { content: cached.content };
}
catch (error) {
this.documentCache.delete(filePath);
return null;
}
}
cacheDocument(filePath, content, lastModified) {
this.documentCache.set(filePath, { content, lastModified });
}
formatResults(results) {
const maxResults = this.config.options?.maxResults || 5;
const sortedResults = results
.sort((a, b) => (b.weight || 0) - (a.weight || 0))
.slice(0, maxResults);
return {
content: sortedResults,
metadata: {
source: this.source,
timestamp: new Date(),
tags: ['knowledge_base', 'documentation'],
resultCount: sortedResults.length,
sources: sortedResults.map(r => r.source)
}
};
}
}
exports.KnowledgeBaseProvider = KnowledgeBaseProvider;
//# sourceMappingURL=KnowledgeBaseProvider.js.map