Add new memory-ruvector extension providing high-performance vector storage and semantic search capabilities using the ruvector database. Features: - Dual-mode operation (remote server or local database) - Automatic message indexing via hooks - Semantic search tool for agents - Multiple embedding providers (OpenAI, Voyage AI, local) - SONA self-learning for improved search accuracy - GNN and Cypher graph queries for relationship traversal - Graceful in-memory fallback - CLI commands for management Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
161 lines
4.5 KiB
TypeScript
161 lines
4.5 KiB
TypeScript
/**
|
|
* RuvectorService - Manages ruvector client lifecycle
|
|
*
|
|
* Handles initialization and cleanup of the ruvector vector database connection.
|
|
* Uses the RuvectorClient wrapper for actual database operations.
|
|
*/
|
|
|
|
import type { PluginLogger } from "clawdbot/plugin-sdk";
|
|
|
|
import { RuvectorClient } from "./client.js";
|
|
import type { RuvectorConfig } from "./config.js";
|
|
import type { RuvectorClientConfig } from "./types.js";
|
|
|
|
// Re-export for backwards compatibility
|
|
export type { RuvectorConfig } from "./config.js";
|
|
|
|
/**
|
|
* Configuration for remote RuvectorService mode (URL-based connection).
|
|
*/
|
|
export type RuvectorServiceConfig = {
|
|
/** Ruvector server URL */
|
|
url: string;
|
|
/** API key for authentication */
|
|
apiKey?: string;
|
|
/** Collection/namespace name */
|
|
collection?: string;
|
|
/** Connection timeout in milliseconds */
|
|
timeoutMs?: number;
|
|
};
|
|
|
|
/**
|
|
* Type guard to check if config is RuvectorServiceConfig (remote mode).
|
|
*/
|
|
function isRemoteConfig(config: RuvectorConfig | RuvectorServiceConfig): config is RuvectorServiceConfig {
|
|
return "url" in config && typeof config.url === "string";
|
|
}
|
|
|
|
/**
|
|
* Service class for managing ruvector vector database connections.
|
|
* Implements the ClawdbotPluginService interface pattern.
|
|
*
|
|
* Supports two modes:
|
|
* - Remote mode (RuvectorServiceConfig): connects to external ruvector server via URL
|
|
* - Local mode (RuvectorConfig): uses local ruvector database with embeddings
|
|
*/
|
|
export class RuvectorService {
|
|
private client: RuvectorClient | null = null;
|
|
private remoteConfig: RuvectorServiceConfig | null = null;
|
|
private logger: PluginLogger;
|
|
private started = false;
|
|
/** Exposed for legacy tool access - use getClient() for typed access */
|
|
readonly url: string;
|
|
readonly collection: string;
|
|
|
|
constructor(config: RuvectorConfig | RuvectorServiceConfig, logger: PluginLogger) {
|
|
this.logger = logger;
|
|
|
|
if (isRemoteConfig(config)) {
|
|
// Remote mode - store config for later connection
|
|
this.remoteConfig = config;
|
|
this.url = config.url;
|
|
this.collection = config.collection ?? "clawdbot-memory";
|
|
// Client will be initialized in start() for remote mode
|
|
// For now, create a placeholder client with default dimension
|
|
const clientConfig: RuvectorClientConfig = {
|
|
dimension: 1536, // Default OpenAI embedding dimension
|
|
metric: "cosine",
|
|
};
|
|
this.client = new RuvectorClient(clientConfig, logger);
|
|
} else {
|
|
// Local mode - create client immediately
|
|
const clientConfig: RuvectorClientConfig = {
|
|
dimension: config.dimension,
|
|
storagePath: config.dbPath,
|
|
metric: config.metric,
|
|
};
|
|
this.client = new RuvectorClient(clientConfig, logger);
|
|
this.url = config.dbPath;
|
|
this.collection = "default";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the ruvector client connection.
|
|
* Called when the plugin service starts.
|
|
*/
|
|
async start(): Promise<void> {
|
|
if (this.started) {
|
|
this.logger.warn("ruvector: service already started");
|
|
return;
|
|
}
|
|
|
|
if (!this.client) {
|
|
throw new Error("ruvector: client not initialized");
|
|
}
|
|
|
|
try {
|
|
await this.client.connect();
|
|
this.started = true;
|
|
} catch (err) {
|
|
this.logger.error(`ruvector: failed to connect: ${String(err)}`);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup and close the ruvector connection.
|
|
* Called when the plugin service stops.
|
|
*/
|
|
async stop(): Promise<void> {
|
|
if (!this.started || !this.client) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await this.client.disconnect();
|
|
this.started = false;
|
|
} catch (err) {
|
|
this.logger.warn(`ruvector: error during disconnect: ${String(err)}`);
|
|
this.started = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the initialized ruvector client.
|
|
* Throws if the service has not been started.
|
|
*/
|
|
getClient(): RuvectorClient {
|
|
if (!this.client) {
|
|
throw new Error("ruvector: client not initialized");
|
|
}
|
|
if (!this.started || !this.client.isConnected()) {
|
|
throw new Error("ruvector: service not started - call start() first");
|
|
}
|
|
return this.client;
|
|
}
|
|
|
|
/**
|
|
* Check if the service is running and connected.
|
|
*/
|
|
isRunning(): boolean {
|
|
return this.started && this.client !== null && this.client.isConnected();
|
|
}
|
|
|
|
/**
|
|
* Legacy compatibility: check connection status.
|
|
* @deprecated Use isRunning() instead.
|
|
*/
|
|
isConnected(): boolean {
|
|
return this.isRunning();
|
|
}
|
|
|
|
/**
|
|
* Legacy compatibility: close the connection.
|
|
* @deprecated Use stop() instead.
|
|
*/
|
|
async close(): Promise<void> {
|
|
return this.stop();
|
|
}
|
|
}
|