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
This commit is contained in:
201
packages/ui/src/services/context-engine/service.ts
Normal file
201
packages/ui/src/services/context-engine/service.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
Reference in New Issue
Block a user