Files
NomadArch/packages/ui/src/services/context-engine/service.ts
Gemini AI 4bd2893864 v0.5.0: Binary-Free Mode - No OpenCode binary required
 Major Features:
- Native session management without OpenCode binary
- Provider routing: OpenCode Zen (free), Qwen OAuth, Z.AI
- Streaming chat with tool execution loop
- Mode detection API (/api/meta/mode)
- MCP integration fix (resolved infinite loading)
- NomadArch Native option in UI with comparison info

🆓 Free Models (No API Key):
- GPT-5 Nano (400K context)
- Grok Code Fast 1 (256K context)
- GLM-4.7 (205K context)
- Doubao Seed Code (256K context)
- Big Pickle (200K context)

📦 New Files:
- session-store.ts: Native session persistence
- native-sessions.ts: REST API for sessions
- lite-mode.ts: UI mode detection client
- native-sessions.ts (UI): SolidJS store

🔧 Updated:
- All installers: Optional binary download
- All launchers: Mode detection display
- Binary selector: Added NomadArch Native option
- README: Binary-Free Mode documentation
2025-12-26 02:12:42 +04:00

202 lines
5.5 KiB
TypeScript

/**
* Context Engine Service
*
* Source: https://github.com/Eskapeum/Context-Engine
*
* Provides intelligent context retrieval for enhanced AI responses:
* - File indexing and caching
* - Semantic search across codebase
* - Q&A memory for persistent knowledge
*/
import { getLogger } from "@/lib/logger";
const log = getLogger("context-engine");
export interface ContextEngineConfig {
projectRoot: string;
enableIndexing?: boolean;
enableVectors?: boolean;
}
export interface RetrievedContext {
content: string;
sources: ContextSource[];
relevanceScore: number;
}
export interface ContextSource {
file: string;
line?: number;
symbol?: string;
type: "code" | "documentation" | "memory" | "qa";
}
export interface IndexStats {
filesIndexed: number;
symbolsFound: number;
lastUpdated: number;
}
// Singleton instance
let instance: ContextEngineService | null = null;
export class ContextEngineService {
private config: ContextEngineConfig;
private initialized: boolean = false;
private stats: IndexStats = { filesIndexed: 0, symbolsFound: 0, lastUpdated: 0 };
private memoryCache: Map<string, { question: string; answer: string; timestamp: number }> = new Map();
constructor(config: ContextEngineConfig) {
this.config = config;
}
/**
* Initialize the context engine
*/
async initialize(): Promise<void> {
if (this.initialized) return;
log.info("Context Engine initializing...", { projectRoot: this.config.projectRoot });
// In a full implementation, this would:
// 1. Scan the project directory
// 2. Build Tree-sitter AST for each file
// 3. Create embeddings for semantic search
this.initialized = true;
log.info("Context Engine initialized");
}
/**
* Retrieve relevant context for a query
*/
async retrieve(query: string, options?: { maxTokens?: number }): Promise<RetrievedContext> {
if (!this.initialized) {
await this.initialize();
}
log.info("Retrieving context for query", { query: query.substring(0, 50) });
// Search memory cache first
const memorySuggestions = this.searchMemory(query);
// In a full implementation, this would:
// 1. Vectorize the query
// 2. Search the index for relevant files/symbols
// 3. Rank results by relevance
// 4. Return top matches within token budget
return {
content: memorySuggestions.join("\n\n"),
sources: [],
relevanceScore: 0
};
}
/**
* Index or re-index the project
*/
async index(options?: { force?: boolean }): Promise<IndexStats> {
log.info("Indexing project...", { force: options?.force });
// In a full implementation, this would:
// 1. Walk the file tree
// 2. Parse each file with Tree-sitter
// 3. Extract symbols and documentation
// 4. Generate embeddings
this.stats = {
filesIndexed: 0,
symbolsFound: 0,
lastUpdated: Date.now()
};
return this.stats;
}
/**
* Get current index stats
*/
getStats(): IndexStats {
return this.stats;
}
/**
* Add to Q&A memory
*/
async remember(question: string, answer: string): Promise<void> {
const id = `qa_${Date.now()}`;
this.memoryCache.set(id, {
question,
answer,
timestamp: Date.now()
});
log.info("Remembered Q&A", { question: question.substring(0, 50) });
}
/**
* Search Q&A memory
*/
searchMemory(query: string): string[] {
const results: string[] = [];
const queryLower = query.toLowerCase();
for (const [, entry] of this.memoryCache) {
if (entry.question.toLowerCase().includes(queryLower) ||
entry.answer.toLowerCase().includes(queryLower)) {
results.push(`Q: ${entry.question}\nA: ${entry.answer}`);
}
}
return results.slice(0, 5);
}
/**
* Search Q&A memory (async version)
*/
async recall(query: string): Promise<{ question: string; answer: string }[]> {
log.info("Recalling from memory", { query: query.substring(0, 50) });
const results: { question: string; answer: string }[] = [];
const queryLower = query.toLowerCase();
for (const [, entry] of this.memoryCache) {
if (entry.question.toLowerCase().includes(queryLower) ||
entry.answer.toLowerCase().includes(queryLower)) {
results.push({ question: entry.question, answer: entry.answer });
}
}
return results.slice(0, 10);
}
}
/**
* Get or create context engine instance
*/
export function getContextEngine(config?: ContextEngineConfig): ContextEngineService {
if (!instance && config) {
instance = new ContextEngineService(config);
}
if (!instance) {
throw new Error("Context engine not initialized. Provide config on first call.");
}
return instance;
}
/**
* Initialize context engine for a workspace
*/
export async function initializeContextEngine(projectRoot: string): Promise<ContextEngineService> {
const service = getContextEngine({ projectRoot });
await service.initialize();
return service;
}
export default {
ContextEngineService,
getContextEngine,
initializeContextEngine,
};