@stackmemoryai/stackmemory 0.6.3 → 0.8.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/dist/src/cli/commands/clear.js +10 -15
- package/dist/src/cli/commands/clear.js.map +2 -2
- package/dist/src/cli/commands/daemon.js +31 -0
- package/dist/src/cli/commands/daemon.js.map +2 -2
- package/dist/src/core/context/refactored-frame-manager.js +7 -0
- package/dist/src/core/context/refactored-frame-manager.js.map +2 -2
- package/dist/src/core/database/database-adapter.js +45 -0
- package/dist/src/core/database/database-adapter.js.map +2 -2
- package/dist/src/core/database/embedding-provider-factory.js +73 -0
- package/dist/src/core/database/embedding-provider-factory.js.map +7 -0
- package/dist/src/core/database/ollama-embedding-provider.js +31 -0
- package/dist/src/core/database/ollama-embedding-provider.js.map +7 -0
- package/dist/src/core/database/openai-embedding-provider.js +44 -0
- package/dist/src/core/database/openai-embedding-provider.js.map +7 -0
- package/dist/src/core/database/sqlite-adapter.js +419 -21
- package/dist/src/core/database/sqlite-adapter.js.map +3 -3
- package/dist/src/core/retrieval/context-retriever.js +20 -3
- package/dist/src/core/retrieval/context-retriever.js.map +2 -2
- package/dist/src/core/session/enhanced-handoff.js +1 -1
- package/dist/src/core/session/enhanced-handoff.js.map +2 -2
- package/dist/src/core/storage/project-registry.js +61 -0
- package/dist/src/core/storage/project-registry.js.map +7 -0
- package/dist/src/core/storage/storage-tier-manager.js +214 -0
- package/dist/src/core/storage/storage-tier-manager.js.map +7 -0
- package/dist/src/daemon/daemon-config.js +11 -0
- package/dist/src/daemon/daemon-config.js.map +2 -2
- package/dist/src/daemon/services/maintenance-service.js +110 -3
- package/dist/src/daemon/services/maintenance-service.js.map +2 -2
- package/dist/src/daemon/services/memory-service.js +190 -0
- package/dist/src/daemon/services/memory-service.js.map +7 -0
- package/dist/src/daemon/unified-daemon.js +39 -5
- package/dist/src/daemon/unified-daemon.js.map +2 -2
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/core/database/database-adapter.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Database Adapter Interface\n * Provides abstraction layer for different database implementations\n * Supports SQLite (current) and ParadeDB (new) with seamless migration\n */\n\nimport type { Frame, Event, Anchor } from '../context/index.js';\nimport type { EmbeddingProvider } from './embedding-provider.js';\n\nexport interface QueryOptions {\n limit?: number;\n offset?: number;\n orderBy?: string;\n orderDirection?: 'ASC' | 'DESC';\n timeout?: number;\n}\n\nexport interface SearchOptions extends QueryOptions {\n query: string;\n searchType?: 'text' | 'vector' | 'hybrid';\n scoreThreshold?: number;\n fields?: string[];\n boost?: Record<string, number>;\n}\n\nexport interface AggregationOptions {\n groupBy: string[];\n metrics: Array<{\n field: string;\n operation: 'count' | 'sum' | 'avg' | 'min' | 'max';\n alias?: string;\n }>;\n having?: Record<string, any>;\n}\n\nexport interface BulkOperation {\n type: 'insert' | 'update' | 'delete';\n table: string;\n data?: any;\n where?: Record<string, any>;\n}\n\nexport interface DatabaseStats {\n totalFrames: number;\n activeFrames: number;\n totalEvents: number;\n totalAnchors: number;\n diskUsage: number;\n lastVacuum?: Date;\n}\n\n// Database result type interfaces\nexport interface CountResult {\n count: number;\n}\n\nexport interface VersionResult {\n version: number;\n}\n\nexport interface FrameRow {\n frame_id: string;\n parent_frame_id?: string;\n project_id: string;\n run_id: string;\n type: string;\n name: string;\n state: string;\n depth: number;\n inputs: string;\n outputs: string;\n digest_text: string;\n digest_json: string;\n created_at: number;\n closed_at?: number;\n score?: number;\n}\n\nexport interface EventRow {\n event_id: string;\n frame_id: string;\n seq: number;\n type: string;\n text: string;\n metadata: string;\n}\n\nexport interface AnchorRow {\n anchor_id: string;\n frame_id: string;\n type: string;\n text: string;\n priority: number;\n created_at: number;\n metadata: string;\n}\n\nexport abstract class DatabaseAdapter {\n protected readonly projectId: string;\n protected readonly config: any;\n\n constructor(projectId: string, config?: any) {\n this.projectId = projectId;\n this.config = config || {};\n }\n\n // Lifecycle methods\n abstract connect(): Promise<void>;\n abstract disconnect(): Promise<void>;\n abstract isConnected(): boolean;\n abstract ping(): Promise<boolean>;\n\n // Schema management\n abstract initializeSchema(): Promise<void>;\n abstract migrateSchema(targetVersion: number): Promise<void>;\n abstract getSchemaVersion(): Promise<number>;\n\n // Frame operations\n abstract createFrame(frame: Partial<Frame>): Promise<string>;\n abstract getFrame(frameId: string): Promise<Frame | null>;\n abstract updateFrame(frameId: string, updates: Partial<Frame>): Promise<void>;\n abstract deleteFrame(frameId: string): Promise<void>;\n abstract getActiveFrames(runId?: string): Promise<Frame[]>;\n abstract closeFrame(frameId: string, outputs?: any): Promise<void>;\n\n // Event operations\n abstract createEvent(event: Partial<Event>): Promise<string>;\n abstract getFrameEvents(\n frameId: string,\n options?: QueryOptions\n ): Promise<Event[]>;\n abstract deleteFrameEvents(frameId: string): Promise<void>;\n\n // Anchor operations\n abstract createAnchor(anchor: Partial<Anchor>): Promise<string>;\n abstract getFrameAnchors(frameId: string): Promise<Anchor[]>;\n abstract deleteFrameAnchors(frameId: string): Promise<void>;\n\n // Search operations (enhanced for ParadeDB)\n abstract search(\n options: SearchOptions\n ): Promise<Array<Frame & { score: number }>>;\n abstract searchByVector(\n embedding: number[],\n options?: QueryOptions\n ): Promise<Array<Frame & { similarity: number }>>;\n abstract searchHybrid(\n textQuery: string,\n embedding: number[],\n weights?: { text: number; vector: number }\n ): Promise<Array<Frame & { score: number }>>;\n\n // Aggregation operations\n abstract aggregate(\n table: string,\n options: AggregationOptions\n ): Promise<Record<string, any>[]>;\n\n // Pattern detection\n abstract detectPatterns(timeRange?: { start: Date; end: Date }): Promise<\n Array<{\n pattern: string;\n type: string;\n frequency: number;\n lastSeen: Date;\n }>\n >;\n\n // Bulk operations\n abstract executeBulk(operations: BulkOperation[]): Promise<void>;\n abstract vacuum(): Promise<void>;\n abstract analyze(): Promise<void>;\n\n // Statistics\n abstract getStats(): Promise<DatabaseStats>;\n abstract getQueryStats(): Promise<\n Array<{\n query: string;\n calls: number;\n meanTime: number;\n totalTime: number;\n }>\n >;\n\n // Transaction support\n abstract beginTransaction(): Promise<void>;\n abstract commitTransaction(): Promise<void>;\n abstract rollbackTransaction(): Promise<void>;\n abstract inTransaction(\n callback: (adapter: DatabaseAdapter) => Promise<void>\n ): Promise<void>;\n\n // Export/Import for migration\n abstract exportData(\n tables: string[],\n format: 'json' | 'parquet' | 'csv'\n ): Promise<Buffer>;\n abstract importData(\n data: Buffer,\n format: 'json' | 'parquet' | 'csv',\n options?: { truncate?: boolean; upsert?: boolean }\n ): Promise<void>;\n\n /** Returns the configured embedding provider, if any */\n getEmbeddingProvider(): EmbeddingProvider | undefined {\n return undefined;\n }\n\n // Utility methods\n protected generateId(): string {\n return crypto.randomUUID();\n }\n\n protected sanitizeQuery(query: string): string {\n // DEPRECATED: Use parameterized queries instead\n // This method is kept for legacy compatibility but should not be used\n console.warn(\n 'sanitizeQuery() is deprecated and unsafe - use parameterized queries'\n );\n return query.replace(/[;'\"\\\\]/g, '');\n }\n\n protected buildWhereClause(conditions: Record<string, any>): string {\n const clauses = Object.entries(conditions).map(([key, value]) => {\n if (value === null) {\n return `${key} IS NULL`;\n } else if (Array.isArray(value)) {\n return `${key} IN (${value.map(() => '?').join(',')})`;\n } else {\n return `${key} = ?`;\n }\n });\n return clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : '';\n }\n\n protected buildOrderByClause(\n orderBy?: string,\n direction?: 'ASC' | 'DESC'\n ): string {\n if (!orderBy) return '';\n // Whitelist validation: allow letters, numbers, underscore, dot (for table aliasing)\n const isSafe = /^[a-zA-Z0-9_.]+$/.test(orderBy);\n if (!isSafe) {\n // Drop ORDER BY if unsafe to prevent injection via column name\n return '';\n }\n const dir = direction === 'DESC' ? 'DESC' : 'ASC';\n return ` ORDER BY ${orderBy} ${dir}`;\n }\n\n protected buildLimitClause(limit?: number, offset?: number): string {\n if (!limit) return '';\n let clause = ` LIMIT ${limit}`;\n if (offset) clause += ` OFFSET ${offset}`;\n return clause;\n }\n}\n\n// Feature flags for gradual migration\nexport interface DatabaseFeatures {\n supportsFullTextSearch: boolean;\n supportsVectorSearch: boolean;\n supportsPartitioning: boolean;\n supportsAnalytics: boolean;\n supportsCompression: boolean;\n supportsMaterializedViews: boolean;\n supportsParallelQueries: boolean;\n}\n\nexport abstract class FeatureAwareDatabaseAdapter extends DatabaseAdapter {\n abstract getFeatures(): DatabaseFeatures;\n\n async canUseFeature(feature: keyof DatabaseFeatures): Promise<boolean> {\n const features = this.getFeatures();\n return features[feature] || false;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;
|
|
4
|
+
"sourcesContent": ["/**\n * Database Adapter Interface\n * Provides abstraction layer for different database implementations\n * Supports SQLite (current) and ParadeDB (new) with seamless migration\n */\n\nimport type { Frame, Event, Anchor } from '../context/index.js';\nimport type { EmbeddingProvider } from './embedding-provider.js';\n\nexport interface QueryOptions {\n limit?: number;\n offset?: number;\n orderBy?: string;\n orderDirection?: 'ASC' | 'DESC';\n timeout?: number;\n}\n\nexport interface SearchOptions extends QueryOptions {\n query: string;\n searchType?: 'text' | 'vector' | 'hybrid';\n scoreThreshold?: number;\n fields?: string[];\n boost?: Record<string, number>;\n projectId?: string; // filter results to specific project\n}\n\nexport interface AggregationOptions {\n groupBy: string[];\n metrics: Array<{\n field: string;\n operation: 'count' | 'sum' | 'avg' | 'min' | 'max';\n alias?: string;\n }>;\n having?: Record<string, any>;\n}\n\nexport interface BulkOperation {\n type: 'insert' | 'update' | 'delete';\n table: string;\n data?: any;\n where?: Record<string, any>;\n}\n\nexport interface DatabaseStats {\n totalFrames: number;\n activeFrames: number;\n totalEvents: number;\n totalAnchors: number;\n diskUsage: number;\n lastVacuum?: Date;\n}\n\n// Database result type interfaces\nexport interface CountResult {\n count: number;\n}\n\nexport interface VersionResult {\n version: number;\n}\n\nexport interface FrameRow {\n frame_id: string;\n parent_frame_id?: string;\n project_id: string;\n run_id: string;\n type: string;\n name: string;\n state: string;\n depth: number;\n inputs: string;\n outputs: string;\n digest_text: string;\n digest_json: string;\n created_at: number;\n closed_at?: number;\n score?: number;\n}\n\nexport interface EventRow {\n event_id: string;\n frame_id: string;\n seq: number;\n type: string;\n text: string;\n metadata: string;\n}\n\nexport interface AnchorRow {\n anchor_id: string;\n frame_id: string;\n type: string;\n text: string;\n priority: number;\n created_at: number;\n metadata: string;\n}\n\nexport interface ProjectRegistryRow {\n project_id: string;\n repo_path: string;\n display_name: string | null;\n db_path: string;\n is_active: number;\n created_at: number;\n last_accessed: number;\n}\n\nexport abstract class DatabaseAdapter {\n protected readonly projectId: string;\n protected readonly config: any;\n\n constructor(projectId: string, config?: any) {\n this.projectId = projectId;\n this.config = config || {};\n }\n\n // Lifecycle methods\n abstract connect(): Promise<void>;\n abstract disconnect(): Promise<void>;\n abstract isConnected(): boolean;\n abstract ping(): Promise<boolean>;\n\n // Schema management\n abstract initializeSchema(): Promise<void>;\n abstract migrateSchema(targetVersion: number): Promise<void>;\n abstract getSchemaVersion(): Promise<number>;\n\n // Frame operations\n abstract createFrame(frame: Partial<Frame>): Promise<string>;\n abstract getFrame(frameId: string): Promise<Frame | null>;\n abstract updateFrame(frameId: string, updates: Partial<Frame>): Promise<void>;\n abstract deleteFrame(frameId: string): Promise<void>;\n abstract getActiveFrames(runId?: string): Promise<Frame[]>;\n abstract closeFrame(frameId: string, outputs?: any): Promise<void>;\n\n // Event operations\n abstract createEvent(event: Partial<Event>): Promise<string>;\n abstract getFrameEvents(\n frameId: string,\n options?: QueryOptions\n ): Promise<Event[]>;\n abstract deleteFrameEvents(frameId: string): Promise<void>;\n\n // Anchor operations\n abstract createAnchor(anchor: Partial<Anchor>): Promise<string>;\n abstract getFrameAnchors(frameId: string): Promise<Anchor[]>;\n abstract deleteFrameAnchors(frameId: string): Promise<void>;\n\n // Search operations (enhanced for ParadeDB)\n abstract search(\n options: SearchOptions\n ): Promise<Array<Frame & { score: number }>>;\n abstract searchByVector(\n embedding: number[],\n options?: QueryOptions\n ): Promise<Array<Frame & { similarity: number }>>;\n abstract searchHybrid(\n textQuery: string,\n embedding: number[],\n weights?: { text: number; vector: number },\n mergeStrategy?: 'weighted' | 'rrf'\n ): Promise<Array<Frame & { score: number }>>;\n\n // Aggregation operations\n abstract aggregate(\n table: string,\n options: AggregationOptions\n ): Promise<Record<string, any>[]>;\n\n // Pattern detection\n abstract detectPatterns(timeRange?: { start: Date; end: Date }): Promise<\n Array<{\n pattern: string;\n type: string;\n frequency: number;\n lastSeen: Date;\n }>\n >;\n\n // Bulk operations\n abstract executeBulk(operations: BulkOperation[]): Promise<void>;\n abstract vacuum(): Promise<void>;\n abstract analyze(): Promise<void>;\n\n // Statistics\n abstract getStats(): Promise<DatabaseStats>;\n abstract getQueryStats(): Promise<\n Array<{\n query: string;\n calls: number;\n meanTime: number;\n totalTime: number;\n }>\n >;\n\n // Transaction support\n abstract beginTransaction(): Promise<void>;\n abstract commitTransaction(): Promise<void>;\n abstract rollbackTransaction(): Promise<void>;\n abstract inTransaction(\n callback: (adapter: DatabaseAdapter) => Promise<void>\n ): Promise<void>;\n\n // Export/Import for migration\n abstract exportData(\n tables: string[],\n format: 'json' | 'parquet' | 'csv'\n ): Promise<Buffer>;\n abstract importData(\n data: Buffer,\n format: 'json' | 'parquet' | 'csv',\n options?: { truncate?: boolean; upsert?: boolean }\n ): Promise<void>;\n\n /** Returns the configured embedding provider, if any */\n getEmbeddingProvider(): EmbeddingProvider | undefined {\n return undefined;\n }\n\n // Maintenance state (no-op defaults, overridden in SQLiteAdapter)\n async getMaintenanceState(_key: string): Promise<string | null> {\n return null;\n }\n\n async setMaintenanceState(_key: string, _value: string): Promise<void> {\n // No-op default \u2014 subclasses may override\n }\n\n // Garbage collection (no-op default, overridden in SQLiteAdapter)\n async runGC(_options?: {\n retentionDays?: number;\n batchSize?: number;\n dryRun?: boolean;\n }): Promise<{\n framesDeleted: number;\n eventsDeleted: number;\n anchorsDeleted: number;\n embeddingsDeleted: number;\n ftsEntriesDeleted: number;\n }> {\n return {\n framesDeleted: 0,\n eventsDeleted: 0,\n anchorsDeleted: 0,\n embeddingsDeleted: 0,\n ftsEntriesDeleted: 0,\n };\n }\n\n // Retrieval logging (no-op defaults, overridden in SQLiteAdapter)\n async logRetrieval(_entry: {\n queryText: string;\n strategy: string;\n resultsCount: number;\n topScore: number | null;\n latencyMs: number;\n resultFrameIds: string[];\n }): Promise<void> {\n // No-op default \u2014 subclasses may override\n }\n\n async getRetrievalStats(_sinceDays?: number): Promise<{\n totalQueries: number;\n avgLatencyMs: number;\n p95LatencyMs: number;\n strategyDistribution: Record<string, number>;\n avgResultsCount: number;\n queriesWithNoResults: number;\n }> {\n // No-op default\n return {\n totalQueries: 0,\n avgLatencyMs: 0,\n p95LatencyMs: 0,\n strategyDistribution: {},\n avgResultsCount: 0,\n queriesWithNoResults: 0,\n };\n }\n\n // Project registry (no-op defaults, overridden in SQLiteAdapter)\n async registerProject(_project: {\n projectId: string;\n repoPath: string;\n displayName?: string;\n dbPath: string;\n }): Promise<void> {\n // No-op default\n }\n\n async getRegisteredProjects(): Promise<\n Array<{\n projectId: string;\n repoPath: string;\n displayName: string | null;\n dbPath: string;\n isActive: boolean;\n createdAt: number;\n lastAccessed: number;\n }>\n > {\n return [];\n }\n\n async setActiveProject(_projectId: string): Promise<void> {\n // No-op default\n }\n\n async getActiveProject(): Promise<string | null> {\n return null;\n }\n\n async removeProject(_projectId: string): Promise<boolean> {\n return false;\n }\n\n async touchProject(_projectId: string): Promise<void> {\n // No-op default\n }\n\n // Utility methods\n protected generateId(): string {\n return crypto.randomUUID();\n }\n\n protected sanitizeQuery(query: string): string {\n // DEPRECATED: Use parameterized queries instead\n // This method is kept for legacy compatibility but should not be used\n console.warn(\n 'sanitizeQuery() is deprecated and unsafe - use parameterized queries'\n );\n return query.replace(/[;'\"\\\\]/g, '');\n }\n\n protected buildWhereClause(conditions: Record<string, any>): string {\n const clauses = Object.entries(conditions).map(([key, value]) => {\n if (value === null) {\n return `${key} IS NULL`;\n } else if (Array.isArray(value)) {\n return `${key} IN (${value.map(() => '?').join(',')})`;\n } else {\n return `${key} = ?`;\n }\n });\n return clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : '';\n }\n\n protected buildOrderByClause(\n orderBy?: string,\n direction?: 'ASC' | 'DESC'\n ): string {\n if (!orderBy) return '';\n // Whitelist validation: allow letters, numbers, underscore, dot (for table aliasing)\n const isSafe = /^[a-zA-Z0-9_.]+$/.test(orderBy);\n if (!isSafe) {\n // Drop ORDER BY if unsafe to prevent injection via column name\n return '';\n }\n const dir = direction === 'DESC' ? 'DESC' : 'ASC';\n return ` ORDER BY ${orderBy} ${dir}`;\n }\n\n protected buildLimitClause(limit?: number, offset?: number): string {\n if (!limit) return '';\n let clause = ` LIMIT ${limit}`;\n if (offset) clause += ` OFFSET ${offset}`;\n return clause;\n }\n}\n\n// Feature flags for gradual migration\nexport interface DatabaseFeatures {\n supportsFullTextSearch: boolean;\n supportsVectorSearch: boolean;\n supportsPartitioning: boolean;\n supportsAnalytics: boolean;\n supportsCompression: boolean;\n supportsMaterializedViews: boolean;\n supportsParallelQueries: boolean;\n}\n\nexport abstract class FeatureAwareDatabaseAdapter extends DatabaseAdapter {\n abstract getFeatures(): DatabaseFeatures;\n\n async canUseFeature(feature: keyof DatabaseFeatures): Promise<boolean> {\n const features = this.getFeatures();\n return features[feature] || false;\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;AA4GO,MAAe,gBAAgB;AAAA,EACjB;AAAA,EACA;AAAA,EAEnB,YAAY,WAAmB,QAAc;AAC3C,SAAK,YAAY;AACjB,SAAK,SAAS,UAAU,CAAC;AAAA,EAC3B;AAAA;AAAA,EAqGA,uBAAsD;AACpD,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,oBAAoB,MAAsC;AAC9D,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,oBAAoB,MAAc,QAA+B;AAAA,EAEvE;AAAA;AAAA,EAGA,MAAM,MAAM,UAUT;AACD,WAAO;AAAA,MACL,eAAe;AAAA,MACf,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,mBAAmB;AAAA,MACnB,mBAAmB;AAAA,IACrB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,aAAa,QAOD;AAAA,EAElB;AAAA,EAEA,MAAM,kBAAkB,YAOrB;AAED,WAAO;AAAA,MACL,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,sBAAsB,CAAC;AAAA,MACvB,iBAAiB;AAAA,MACjB,sBAAsB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,gBAAgB,UAKJ;AAAA,EAElB;AAAA,EAEA,MAAM,wBAUJ;AACA,WAAO,CAAC;AAAA,EACV;AAAA,EAEA,MAAM,iBAAiB,YAAmC;AAAA,EAE1D;AAAA,EAEA,MAAM,mBAA2C;AAC/C,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,cAAc,YAAsC;AACxD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAAa,YAAmC;AAAA,EAEtD;AAAA;AAAA,EAGU,aAAqB;AAC7B,WAAO,OAAO,WAAW;AAAA,EAC3B;AAAA,EAEU,cAAc,OAAuB;AAG7C,YAAQ;AAAA,MACN;AAAA,IACF;AACA,WAAO,MAAM,QAAQ,YAAY,EAAE;AAAA,EACrC;AAAA,EAEU,iBAAiB,YAAyC;AAClE,UAAM,UAAU,OAAO,QAAQ,UAAU,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AAC/D,UAAI,UAAU,MAAM;AAClB,eAAO,GAAG,GAAG;AAAA,MACf,WAAW,MAAM,QAAQ,KAAK,GAAG;AAC/B,eAAO,GAAG,GAAG,QAAQ,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,MACrD,OAAO;AACL,eAAO,GAAG,GAAG;AAAA,MACf;AAAA,IACF,CAAC;AACD,WAAO,QAAQ,SAAS,IAAI,SAAS,QAAQ,KAAK,OAAO,CAAC,KAAK;AAAA,EACjE;AAAA,EAEU,mBACR,SACA,WACQ;AACR,QAAI,CAAC,QAAS,QAAO;AAErB,UAAM,SAAS,mBAAmB,KAAK,OAAO;AAC9C,QAAI,CAAC,QAAQ;AAEX,aAAO;AAAA,IACT;AACA,UAAM,MAAM,cAAc,SAAS,SAAS;AAC5C,WAAO,aAAa,OAAO,IAAI,GAAG;AAAA,EACpC;AAAA,EAEU,iBAAiB,OAAgB,QAAyB;AAClE,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,SAAS,UAAU,KAAK;AAC5B,QAAI,OAAQ,WAAU,WAAW,MAAM;AACvC,WAAO;AAAA,EACT;AACF;AAaO,MAAe,oCAAoC,gBAAgB;AAAA,EAGxE,MAAM,cAAc,SAAmD;AACrE,UAAM,WAAW,KAAK,YAAY;AAClC,WAAO,SAAS,OAAO,KAAK;AAAA,EAC9B;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { OllamaEmbeddingProvider } from "./ollama-embedding-provider.js";
|
|
6
|
+
import { OpenAIEmbeddingProvider } from "./openai-embedding-provider.js";
|
|
7
|
+
class EmbeddingProviderFactory {
|
|
8
|
+
/**
|
|
9
|
+
* Create an embedding provider based on config.
|
|
10
|
+
* Tries the primary provider first, then fallbacks in order.
|
|
11
|
+
* Returns null if no provider is available.
|
|
12
|
+
*/
|
|
13
|
+
static async create(config) {
|
|
14
|
+
const providers = [
|
|
15
|
+
config.provider,
|
|
16
|
+
...config.fallbackProviders ?? []
|
|
17
|
+
];
|
|
18
|
+
for (const p of providers) {
|
|
19
|
+
if (p === "none") return null;
|
|
20
|
+
try {
|
|
21
|
+
const provider = await EmbeddingProviderFactory.tryCreate(p, config);
|
|
22
|
+
if (provider) return provider;
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
static async tryCreate(type, config) {
|
|
29
|
+
switch (type) {
|
|
30
|
+
case "transformers": {
|
|
31
|
+
const { createTransformersProvider } = await import("./transformers-embedding-provider.js");
|
|
32
|
+
return createTransformersProvider(config.model, config.dimension);
|
|
33
|
+
}
|
|
34
|
+
case "ollama":
|
|
35
|
+
return EmbeddingProviderFactory.createOllamaProvider(config);
|
|
36
|
+
case "openai":
|
|
37
|
+
return EmbeddingProviderFactory.createOpenAIProvider(config);
|
|
38
|
+
case "none":
|
|
39
|
+
return null;
|
|
40
|
+
default:
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
static async createOllamaProvider(config) {
|
|
45
|
+
try {
|
|
46
|
+
const baseUrl = config.baseUrl ?? "http://localhost:11434";
|
|
47
|
+
const response = await fetch(`${baseUrl}/api/tags`, {
|
|
48
|
+
signal: AbortSignal.timeout(2e3)
|
|
49
|
+
});
|
|
50
|
+
if (!response.ok) return null;
|
|
51
|
+
return new OllamaEmbeddingProvider(
|
|
52
|
+
baseUrl,
|
|
53
|
+
config.model ?? "nomic-embed-text",
|
|
54
|
+
config.dimension ?? 768
|
|
55
|
+
);
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
static async createOpenAIProvider(config) {
|
|
61
|
+
const apiKey = config.apiKey ?? process.env["OPENAI_API_KEY"];
|
|
62
|
+
if (!apiKey) return null;
|
|
63
|
+
return new OpenAIEmbeddingProvider(
|
|
64
|
+
apiKey,
|
|
65
|
+
config.model ?? "text-embedding-3-small",
|
|
66
|
+
config.dimension ?? 1536
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
EmbeddingProviderFactory
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=embedding-provider-factory.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/core/database/embedding-provider-factory.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Embedding Provider Factory\n * Config-driven creation with fallback chain support.\n * Never throws \u2014 returns null when no provider is available.\n */\n\nimport type { EmbeddingProvider } from './embedding-provider.js';\nimport { OllamaEmbeddingProvider } from './ollama-embedding-provider.js';\nimport { OpenAIEmbeddingProvider } from './openai-embedding-provider.js';\n\nexport type EmbeddingProviderType =\n | 'transformers'\n | 'ollama'\n | 'openai'\n | 'none';\n\nexport interface EmbeddingProviderConfig {\n provider: EmbeddingProviderType;\n model?: string;\n dimension?: number;\n apiKey?: string;\n baseUrl?: string;\n fallbackProviders?: EmbeddingProviderType[];\n}\n\nexport class EmbeddingProviderFactory {\n /**\n * Create an embedding provider based on config.\n * Tries the primary provider first, then fallbacks in order.\n * Returns null if no provider is available.\n */\n static async create(\n config: EmbeddingProviderConfig\n ): Promise<EmbeddingProvider | null> {\n const providers: EmbeddingProviderType[] = [\n config.provider,\n ...(config.fallbackProviders ?? []),\n ];\n\n for (const p of providers) {\n if (p === 'none') return null;\n try {\n const provider = await EmbeddingProviderFactory.tryCreate(p, config);\n if (provider) return provider;\n } catch {\n // Swallow \u2014 try next fallback\n }\n }\n return null;\n }\n\n private static async tryCreate(\n type: EmbeddingProviderType,\n config: EmbeddingProviderConfig\n ): Promise<EmbeddingProvider | null> {\n switch (type) {\n case 'transformers': {\n const { createTransformersProvider } =\n await import('./transformers-embedding-provider.js');\n return createTransformersProvider(config.model, config.dimension);\n }\n case 'ollama':\n return EmbeddingProviderFactory.createOllamaProvider(config);\n case 'openai':\n return EmbeddingProviderFactory.createOpenAIProvider(config);\n case 'none':\n return null;\n default:\n return null;\n }\n }\n\n private static async createOllamaProvider(\n config: EmbeddingProviderConfig\n ): Promise<EmbeddingProvider | null> {\n try {\n const baseUrl = config.baseUrl ?? 'http://localhost:11434';\n const response = await fetch(`${baseUrl}/api/tags`, {\n signal: AbortSignal.timeout(2000),\n });\n if (!response.ok) return null;\n return new OllamaEmbeddingProvider(\n baseUrl,\n config.model ?? 'nomic-embed-text',\n config.dimension ?? 768\n );\n } catch {\n return null;\n }\n }\n\n private static async createOpenAIProvider(\n config: EmbeddingProviderConfig\n ): Promise<EmbeddingProvider | null> {\n const apiKey = config.apiKey ?? process.env['OPENAI_API_KEY'];\n if (!apiKey) return null;\n return new OpenAIEmbeddingProvider(\n apiKey,\n config.model ?? 'text-embedding-3-small',\n config.dimension ?? 1536\n );\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;AAOA,SAAS,+BAA+B;AACxC,SAAS,+BAA+B;AAiBjC,MAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMpC,aAAa,OACX,QACmC;AACnC,UAAM,YAAqC;AAAA,MACzC,OAAO;AAAA,MACP,GAAI,OAAO,qBAAqB,CAAC;AAAA,IACnC;AAEA,eAAW,KAAK,WAAW;AACzB,UAAI,MAAM,OAAQ,QAAO;AACzB,UAAI;AACF,cAAM,WAAW,MAAM,yBAAyB,UAAU,GAAG,MAAM;AACnE,YAAI,SAAU,QAAO;AAAA,MACvB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,aAAqB,UACnB,MACA,QACmC;AACnC,YAAQ,MAAM;AAAA,MACZ,KAAK,gBAAgB;AACnB,cAAM,EAAE,2BAA2B,IACjC,MAAM,OAAO,sCAAsC;AACrD,eAAO,2BAA2B,OAAO,OAAO,OAAO,SAAS;AAAA,MAClE;AAAA,MACA,KAAK;AACH,eAAO,yBAAyB,qBAAqB,MAAM;AAAA,MAC7D,KAAK;AACH,eAAO,yBAAyB,qBAAqB,MAAM;AAAA,MAC7D,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEA,aAAqB,qBACnB,QACmC;AACnC,QAAI;AACF,YAAM,UAAU,OAAO,WAAW;AAClC,YAAM,WAAW,MAAM,MAAM,GAAG,OAAO,aAAa;AAAA,QAClD,QAAQ,YAAY,QAAQ,GAAI;AAAA,MAClC,CAAC;AACD,UAAI,CAAC,SAAS,GAAI,QAAO;AACzB,aAAO,IAAI;AAAA,QACT;AAAA,QACA,OAAO,SAAS;AAAA,QAChB,OAAO,aAAa;AAAA,MACtB;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,aAAqB,qBACnB,QACmC;AACnC,UAAM,SAAS,OAAO,UAAU,QAAQ,IAAI,gBAAgB;AAC5D,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,IAAI;AAAA,MACT;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,OAAO,aAAa;AAAA,IACtB;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
class OllamaEmbeddingProvider {
|
|
6
|
+
dimension;
|
|
7
|
+
baseUrl;
|
|
8
|
+
model;
|
|
9
|
+
constructor(baseUrl, model, dimension) {
|
|
10
|
+
this.baseUrl = baseUrl;
|
|
11
|
+
this.model = model;
|
|
12
|
+
this.dimension = dimension;
|
|
13
|
+
}
|
|
14
|
+
async embed(text) {
|
|
15
|
+
const res = await fetch(`${this.baseUrl}/api/embeddings`, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
body: JSON.stringify({ model: this.model, prompt: text })
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) throw new Error(`Ollama embed failed: ${res.status}`);
|
|
21
|
+
const data = await res.json();
|
|
22
|
+
return data.embedding;
|
|
23
|
+
}
|
|
24
|
+
async embedBatch(texts) {
|
|
25
|
+
return Promise.all(texts.map((t) => this.embed(t)));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
OllamaEmbeddingProvider
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=ollama-embedding-provider.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/core/database/ollama-embedding-provider.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Ollama Embedding Provider\n * Generates embeddings via a local Ollama server.\n */\n\nimport type { EmbeddingProvider } from './embedding-provider.js';\n\nexport class OllamaEmbeddingProvider implements EmbeddingProvider {\n readonly dimension: number;\n private baseUrl: string;\n private model: string;\n\n constructor(baseUrl: string, model: string, dimension: number) {\n this.baseUrl = baseUrl;\n this.model = model;\n this.dimension = dimension;\n }\n\n async embed(text: string): Promise<number[]> {\n const res = await fetch(`${this.baseUrl}/api/embeddings`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ model: this.model, prompt: text }),\n });\n if (!res.ok) throw new Error(`Ollama embed failed: ${res.status}`);\n const data = (await res.json()) as { embedding: number[] };\n return data.embedding;\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n return Promise.all(texts.map((t) => this.embed(t)));\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;AAOO,MAAM,wBAAqD;AAAA,EACvD;AAAA,EACD;AAAA,EACA;AAAA,EAER,YAAY,SAAiB,OAAe,WAAmB;AAC7D,SAAK,UAAU;AACf,SAAK,QAAQ;AACb,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,MAAM,MAAiC;AAC3C,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,mBAAmB;AAAA,MACxD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,CAAC;AAAA,IAC1D,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,wBAAwB,IAAI,MAAM,EAAE;AACjE,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,WAAW,OAAsC;AACrD,WAAO,QAAQ,IAAI,MAAM,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC;AAAA,EACpD;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
class OpenAIEmbeddingProvider {
|
|
6
|
+
dimension;
|
|
7
|
+
apiKey;
|
|
8
|
+
model;
|
|
9
|
+
constructor(apiKey, model, dimension) {
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
this.model = model;
|
|
12
|
+
this.dimension = dimension;
|
|
13
|
+
}
|
|
14
|
+
async embed(text) {
|
|
15
|
+
const res = await fetch("https://api.openai.com/v1/embeddings", {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify({ model: this.model, input: text })
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok) throw new Error(`OpenAI embed failed: ${res.status}`);
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
return data.data[0].embedding;
|
|
26
|
+
}
|
|
27
|
+
async embedBatch(texts) {
|
|
28
|
+
const res = await fetch("https://api.openai.com/v1/embeddings", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify({ model: this.model, input: texts })
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok) throw new Error(`OpenAI embed batch failed: ${res.status}`);
|
|
37
|
+
const data = await res.json();
|
|
38
|
+
return data.data.map((d) => d.embedding);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
OpenAIEmbeddingProvider
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=openai-embedding-provider.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/core/database/openai-embedding-provider.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * OpenAI Embedding Provider\n * Generates embeddings via the OpenAI API.\n */\n\nimport type { EmbeddingProvider } from './embedding-provider.js';\n\ninterface OpenAIEmbeddingResponse {\n data: Array<{ embedding: number[] }>;\n}\n\nexport class OpenAIEmbeddingProvider implements EmbeddingProvider {\n readonly dimension: number;\n private apiKey: string;\n private model: string;\n\n constructor(apiKey: string, model: string, dimension: number) {\n this.apiKey = apiKey;\n this.model = model;\n this.dimension = dimension;\n }\n\n async embed(text: string): Promise<number[]> {\n const res = await fetch('https://api.openai.com/v1/embeddings', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({ model: this.model, input: text }),\n });\n if (!res.ok) throw new Error(`OpenAI embed failed: ${res.status}`);\n const data = (await res.json()) as OpenAIEmbeddingResponse;\n return data.data[0]!.embedding;\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n const res = await fetch('https://api.openai.com/v1/embeddings', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({ model: this.model, input: texts }),\n });\n if (!res.ok) throw new Error(`OpenAI embed batch failed: ${res.status}`);\n const data = (await res.json()) as OpenAIEmbeddingResponse;\n return data.data.map((d) => d.embedding);\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;AAWO,MAAM,wBAAqD;AAAA,EACvD;AAAA,EACD;AAAA,EACA;AAAA,EAER,YAAY,QAAgB,OAAe,WAAmB;AAC5D,SAAK,SAAS;AACd,SAAK,QAAQ;AACb,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,MAAM,MAAiC;AAC3C,UAAM,MAAM,MAAM,MAAM,wCAAwC;AAAA,MAC9D,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe,UAAU,KAAK,MAAM;AAAA,MACtC;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,OAAO,KAAK,OAAO,OAAO,KAAK,CAAC;AAAA,IACzD,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,wBAAwB,IAAI,MAAM,EAAE;AACjE,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO,KAAK,KAAK,CAAC,EAAG;AAAA,EACvB;AAAA,EAEA,MAAM,WAAW,OAAsC;AACrD,UAAM,MAAM,MAAM,MAAM,wCAAwC;AAAA,MAC9D,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe,UAAU,KAAK,MAAM;AAAA,MACtC;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,OAAO,KAAK,OAAO,OAAO,MAAM,CAAC;AAAA,IAC1D,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,8BAA8B,IAAI,MAAM,EAAE;AACvE,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO,KAAK,KAAK,IAAI,CAAC,MAAM,EAAE,SAAS;AAAA,EACzC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|