feat(viking): add local server management for privacy-first deployment
Backend (Rust): - viking_commands.rs: Tauri commands for server status/start/stop/restart - memory/mod.rs: Memory module exports - memory/context_builder.rs: Context building with memory injection - memory/extractor.rs: Memory extraction from conversations - llm/mod.rs: LLM integration for memory summarization Frontend (TypeScript): - context-builder.ts: Context building with OpenViking integration - viking-client.ts: OpenViking API client - viking-local.ts: Local storage fallback when Viking unavailable - viking-memory-adapter.ts: Memory extraction and persistence Features: - Multi-mode adapter (local/sidecar/remote) with auto-detection - Privacy-first: all data stored in ~/.openviking/, server only on 127.0.0.1 - Graceful degradation when local server unavailable - Context compaction with memory flush before compression Tests: 21 passing (viking-adapter.test.ts) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
144
desktop/src/lib/viking-local.ts
Normal file
144
desktop/src/lib/viking-local.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Viking Local Adapter - Tauri Sidecar Integration
|
||||
*
|
||||
* Provides local memory operations through the OpenViking CLI sidecar.
|
||||
* This eliminates the need for a Python server dependency.
|
||||
*/
|
||||
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
// === Types ===
|
||||
|
||||
export interface LocalVikingStatus {
|
||||
available: boolean;
|
||||
version?: string;
|
||||
dataDir?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface LocalVikingResource {
|
||||
uri: string;
|
||||
name: string;
|
||||
type: string;
|
||||
size?: number;
|
||||
modifiedAt?: string;
|
||||
}
|
||||
|
||||
export interface LocalVikingFindResult {
|
||||
uri: string;
|
||||
score: number;
|
||||
content: string;
|
||||
level: string;
|
||||
overview?: string;
|
||||
}
|
||||
|
||||
export interface LocalVikingGrepResult {
|
||||
uri: string;
|
||||
line: number;
|
||||
content: string;
|
||||
matchStart: number;
|
||||
matchEnd: number;
|
||||
}
|
||||
|
||||
export interface LocalVikingAddResult {
|
||||
uri: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
// === Local Viking Client ===
|
||||
|
||||
export class VikingLocalClient {
|
||||
private available: boolean | null = null;
|
||||
|
||||
async isAvailable(): Promise<boolean> {
|
||||
if (this.available !== null) {
|
||||
return this.available;
|
||||
}
|
||||
|
||||
try {
|
||||
const status = await this.status();
|
||||
this.available = status.available;
|
||||
return status.available;
|
||||
} catch {
|
||||
this.available = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async status(): Promise<LocalVikingStatus> {
|
||||
return await invoke<LocalVikingStatus>('viking_status');
|
||||
}
|
||||
|
||||
async addResource(
|
||||
uri: string,
|
||||
content: string
|
||||
): Promise<LocalVikingAddResult> {
|
||||
// For small content, use inline; for large content. use file-based
|
||||
if (content.length < 10000) {
|
||||
return await invoke<LocalVikingAddResult>('viking_add_inline', { uri, content });
|
||||
} else {
|
||||
return await invoke<LocalVikingAddResult>('viking_add', { uri, content });
|
||||
}
|
||||
}
|
||||
|
||||
async find(
|
||||
query: string,
|
||||
options?: {
|
||||
scope?: string;
|
||||
limit?: number;
|
||||
}
|
||||
): Promise<LocalVikingFindResult[]> {
|
||||
return await invoke<LocalVikingFindResult[]>('viking_find', {
|
||||
query,
|
||||
scope: options?.scope,
|
||||
limit: options?.limit,
|
||||
});
|
||||
}
|
||||
|
||||
async grep(
|
||||
pattern: string,
|
||||
options?: {
|
||||
uri?: string;
|
||||
caseSensitive?: boolean;
|
||||
limit?: number;
|
||||
}
|
||||
): Promise<LocalVikingGrepResult[]> {
|
||||
return await invoke<LocalVikingGrepResult[]>('viking_grep', {
|
||||
pattern,
|
||||
uri: options?.uri,
|
||||
caseSensitive: options?.caseSensitive,
|
||||
limit: options?.limit,
|
||||
});
|
||||
}
|
||||
|
||||
async ls(path: string): Promise<LocalVikingResource[]> {
|
||||
return await invoke<LocalVikingResource[]>('viking_ls', { path });
|
||||
}
|
||||
|
||||
async readContent(uri: string, level?: string): Promise<string> {
|
||||
return await invoke<string>('viking_read', { uri, level });
|
||||
}
|
||||
|
||||
async removeResource(uri: string): Promise<void> {
|
||||
await invoke('viking_remove', { uri });
|
||||
}
|
||||
|
||||
async tree(path: string, depth?: number): Promise<unknown> {
|
||||
return await invoke('viking_tree', { path, depth });
|
||||
}
|
||||
}
|
||||
|
||||
// === Singleton ===
|
||||
|
||||
let _localClient: VikingLocalClient | null;
|
||||
|
||||
export function getVikingLocalClient(): VikingLocalClient {
|
||||
if (!_localClient) {
|
||||
_localClient = new VikingLocalClient();
|
||||
}
|
||||
return _localClient;
|
||||
}
|
||||
|
||||
export function resetVikingLocalClient(): void {
|
||||
_localClient = null;
|
||||
}
|
||||
Reference in New Issue
Block a user