Initial commit: RA-H Open Source Edition
Local-first knowledge management system with BYO API keys. Features: - 3-panel UI (Nodes | Focus | Helpers) - SQLite + sqlite-vec for vector search - Agent system (Easy/Hard mode orchestrators) - Content extraction (YouTube, PDF, web) - Integrate workflow for connection discovery - Dimension system with auto-assignment Tech stack: - Next.js 15 + TypeScript + Tailwind CSS - Anthropic (Claude) + OpenAI (GPT) via Vercel AI SDK Setup: npm install && npm rebuild better-sqlite3 scripts/dev/bootstrap-local.sh npm run dev MIT License
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// Tool group definitions and utilities
|
||||
export interface ToolGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export const TOOL_GROUPS: Record<string, ToolGroup> = {
|
||||
core: {
|
||||
id: 'core',
|
||||
name: 'Core Graph',
|
||||
description: 'Read-only knowledge graph queries and search (all agents)',
|
||||
icon: '●',
|
||||
color: '#3b82f6'
|
||||
},
|
||||
orchestration: {
|
||||
id: 'orchestration',
|
||||
name: 'Orchestration',
|
||||
description: 'Workflows, web search, and reasoning (orchestrator only)',
|
||||
icon: '●',
|
||||
color: '#8b5cf6'
|
||||
},
|
||||
execution: {
|
||||
id: 'execution',
|
||||
name: 'Execution',
|
||||
description: 'Write operations, extraction, and embeddings (workers only)',
|
||||
icon: '●',
|
||||
color: '#10b981'
|
||||
}
|
||||
};
|
||||
|
||||
// Tool group assignments
|
||||
export const TOOL_GROUP_ASSIGNMENTS: Record<string, string> = {
|
||||
// Core: Read-only graph operations (all agents)
|
||||
queryNodes: 'core',
|
||||
getNodesById: 'core',
|
||||
queryEdge: 'core',
|
||||
searchContentEmbeddings: 'core',
|
||||
|
||||
// Orchestration: Delegation and reasoning (orchestrator only)
|
||||
webSearch: 'orchestration',
|
||||
think: 'orchestration',
|
||||
delegateToMiniRAH: 'orchestration',
|
||||
delegateNodeQuotes: 'orchestration',
|
||||
delegateNodeComparison: 'orchestration',
|
||||
delegateToWiseRAH: 'orchestration',
|
||||
executeWorkflow: 'orchestration',
|
||||
|
||||
// Execution: Write operations and extraction (workers only)
|
||||
createNode: 'execution',
|
||||
updateNode: 'execution',
|
||||
createEdge: 'execution',
|
||||
updateEdge: 'execution',
|
||||
embedContent: 'execution',
|
||||
youtubeExtract: 'execution',
|
||||
websiteExtract: 'execution',
|
||||
paperExtract: 'execution'
|
||||
};
|
||||
|
||||
/**
|
||||
* Get tool group by tool ID
|
||||
*/
|
||||
export function getToolGroup(toolId: string): ToolGroup | null {
|
||||
const groupId = TOOL_GROUP_ASSIGNMENTS[toolId];
|
||||
return groupId ? TOOL_GROUPS[groupId] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group tools by their assigned groups
|
||||
*/
|
||||
export function groupTools(toolIds: string[]): Record<string, string[]> {
|
||||
const grouped: Record<string, string[]> = {};
|
||||
|
||||
toolIds.forEach(toolId => {
|
||||
const groupId = TOOL_GROUP_ASSIGNMENTS[toolId] || 'other';
|
||||
if (!grouped[groupId]) {
|
||||
grouped[groupId] = [];
|
||||
}
|
||||
grouped[groupId].push(toolId);
|
||||
});
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available tools organized by groups
|
||||
*/
|
||||
export function getAllToolsByGroup(): Record<string, string[]> {
|
||||
return groupTools(Object.keys(TOOL_GROUP_ASSIGNMENTS));
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Tool registry exports - main interface for the tool system
|
||||
export { TOOLS, getTool, getTools, getAllTools, getToolSchemas, executeTool, getHelperTools, getToolGroup, groupTools, getAllToolsByGroup } from './registry';
|
||||
|
||||
// Tool types
|
||||
export type { Tool, ToolContext, ToolSchema } from './types';
|
||||
export type { ToolGroup } from './groups';
|
||||
export { TOOL_GROUPS } from './groups';
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Node Formatter Utility
|
||||
* Wraps node data in special markers for UI rendering as labels
|
||||
* Format: [NODE:id:"title"]
|
||||
*/
|
||||
|
||||
export interface NodeData {
|
||||
id: number;
|
||||
title: string;
|
||||
dimensions?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a node for display in chat streams
|
||||
* @param node - Node data to format
|
||||
* @returns Formatted string with node markers
|
||||
*/
|
||||
export function formatNodeForChat(node: NodeData): string {
|
||||
return `[NODE:${node.id}:"${node.title}"]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats multiple nodes for display
|
||||
* @param nodes - Array of nodes to format
|
||||
* @returns Formatted string with each node on a new line
|
||||
*/
|
||||
export function formatNodesForChat(nodes: NodeData[]): string {
|
||||
return nodes.map(formatNodeForChat).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts node data from a formatted string
|
||||
* Used by UI components to parse node markers
|
||||
* @param text - Text potentially containing node markers
|
||||
* @returns Array of parsed node data
|
||||
*/
|
||||
export function parseNodeMarkers(text: string): Array<NodeData & { raw: string }> {
|
||||
const regex = /\[NODE:\s*(\d+)\s*:\s*["“”']([^"“”']+)["“”']\s*\]/g;
|
||||
const nodes: Array<NodeData & { raw: string }> = [];
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
const [raw, id, title] = match;
|
||||
nodes.push({
|
||||
raw,
|
||||
id: parseInt(id, 10),
|
||||
title,
|
||||
dimensions: []
|
||||
});
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import { getToolGroup, groupTools, getAllToolsByGroup } from './groups';
|
||||
import { queryNodesTool } from '../database/queryNodes';
|
||||
import { getNodesByIdTool } from '../database/getNodesById';
|
||||
import { createNodeTool } from '../database/createNode';
|
||||
import { updateNodeTool } from '../database/updateNode';
|
||||
import { createEdgeTool } from '../database/createEdge';
|
||||
import { queryEdgeTool } from '../database/queryEdge';
|
||||
import { updateEdgeTool } from '../database/updateEdge';
|
||||
import { createDimensionTool } from '../database/createDimension';
|
||||
import { updateDimensionTool } from '../database/updateDimension';
|
||||
import { lockDimensionTool } from '../database/lockDimension';
|
||||
import { unlockDimensionTool } from '../database/unlockDimension';
|
||||
import { deleteDimensionTool } from '../database/deleteDimension';
|
||||
import { searchContentEmbeddingsTool } from '../other/searchContentEmbeddings';
|
||||
import { webSearchTool } from '../other/webSearch';
|
||||
import { thinkTool } from '../other/think';
|
||||
import { delegateToMiniRAHTool } from '../orchestration/delegateToMiniRAH';
|
||||
import { delegateNodeQuotesTool, delegateNodeComparisonTool } from '../orchestration/delegationHelpers';
|
||||
import { delegateToWiseRAHTool } from '../orchestration/delegateToWiseRAH';
|
||||
import { executeWorkflowTool } from '../orchestration/executeWorkflow';
|
||||
import { youtubeExtractTool } from '../other/youtubeExtract';
|
||||
import { websiteExtractTool } from '../other/websiteExtract';
|
||||
import { paperExtractTool } from '../other/paperExtract';
|
||||
|
||||
// Core tools available to all agents (read-only graph operations)
|
||||
const CORE_TOOLS: Record<string, any> = {
|
||||
queryNodes: queryNodesTool,
|
||||
getNodesById: getNodesByIdTool,
|
||||
queryEdge: queryEdgeTool,
|
||||
searchContentEmbeddings: searchContentEmbeddingsTool,
|
||||
};
|
||||
|
||||
const ORCHESTRATION_TOOLS: Record<string, any> = {
|
||||
webSearch: webSearchTool,
|
||||
think: thinkTool,
|
||||
delegateToMiniRAH: delegateToMiniRAHTool,
|
||||
delegateNodeQuotes: delegateNodeQuotesTool,
|
||||
delegateNodeComparison: delegateNodeComparisonTool,
|
||||
delegateToWiseRAH: delegateToWiseRAHTool,
|
||||
executeWorkflow: executeWorkflowTool,
|
||||
};
|
||||
|
||||
// Execution tools for worker agents (includes write operations)
|
||||
const EXECUTION_TOOLS: Record<string, any> = {
|
||||
createNode: createNodeTool,
|
||||
updateNode: updateNodeTool,
|
||||
createEdge: createEdgeTool,
|
||||
updateEdge: updateEdgeTool,
|
||||
createDimension: createDimensionTool,
|
||||
updateDimension: updateDimensionTool,
|
||||
lockDimension: lockDimensionTool,
|
||||
unlockDimension: unlockDimensionTool,
|
||||
deleteDimension: deleteDimensionTool,
|
||||
youtubeExtract: youtubeExtractTool,
|
||||
websiteExtract: websiteExtractTool,
|
||||
paperExtract: paperExtractTool,
|
||||
};
|
||||
|
||||
export const TOOL_SETS = {
|
||||
core: CORE_TOOLS,
|
||||
orchestration: ORCHESTRATION_TOOLS,
|
||||
execution: EXECUTION_TOOLS,
|
||||
};
|
||||
|
||||
export const TOOLS: Record<string, any> = {
|
||||
...CORE_TOOLS,
|
||||
...ORCHESTRATION_TOOLS,
|
||||
...EXECUTION_TOOLS,
|
||||
};
|
||||
|
||||
const ORCHESTRATOR_TOOL_NAMES = Array.from(new Set([
|
||||
...Object.keys(CORE_TOOLS),
|
||||
'webSearch',
|
||||
'think',
|
||||
'executeWorkflow',
|
||||
'createNode',
|
||||
'updateNode',
|
||||
'createEdge',
|
||||
'updateEdge',
|
||||
'createDimension',
|
||||
'updateDimension',
|
||||
'lockDimension',
|
||||
'unlockDimension',
|
||||
'deleteDimension',
|
||||
'youtubeExtract',
|
||||
'websiteExtract',
|
||||
'paperExtract',
|
||||
]));
|
||||
|
||||
const EXECUTOR_TOOL_NAMES = [
|
||||
...Object.keys(CORE_TOOLS),
|
||||
...Object.keys(ORCHESTRATION_TOOLS).filter(name =>
|
||||
name !== 'delegateToMiniRAH' &&
|
||||
name !== 'delegateToWiseRAH' &&
|
||||
name !== 'executeWorkflow' &&
|
||||
name !== 'delegateNodeQuotes' &&
|
||||
name !== 'delegateNodeComparison'
|
||||
),
|
||||
...Object.keys(EXECUTION_TOOLS),
|
||||
];
|
||||
|
||||
const PLANNER_TOOL_NAMES = [
|
||||
...Object.keys(CORE_TOOLS),
|
||||
'webSearch',
|
||||
'think',
|
||||
'delegateToMiniRAH',
|
||||
'updateNode', // For workflow execution (integrate workflow needs direct write access)
|
||||
];
|
||||
|
||||
/**
|
||||
* Get tool by ID
|
||||
*/
|
||||
export function getTool(toolId: string): any | null {
|
||||
return TOOLS[toolId] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools by IDs (for helper's available_tools)
|
||||
*/
|
||||
export function getTools(toolIds: string[]): any[] {
|
||||
if (!Array.isArray(toolIds)) {
|
||||
console.error('getTools received non-array:', toolIds);
|
||||
return [];
|
||||
}
|
||||
return toolIds.map(id => TOOLS[id]).filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available tools
|
||||
*/
|
||||
export function getAllTools(): any[] {
|
||||
return Object.values(TOOLS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tool schemas for OpenAI function calling
|
||||
*/
|
||||
export function getToolSchemas(toolIds: string[]) {
|
||||
return getTools(toolIds).map(tool => tool.schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools for a specific helper by tool names
|
||||
* This is the main function used by helper APIs to get their assigned tools
|
||||
*/
|
||||
export function getHelperTools(availableToolNames: string[]): Record<string, any> {
|
||||
if (!Array.isArray(availableToolNames)) {
|
||||
console.error('getHelperTools received non-array:', availableToolNames);
|
||||
return {};
|
||||
}
|
||||
|
||||
return availableToolNames.reduce((tools, name) => {
|
||||
if (TOOLS[name]) {
|
||||
tools[name] = TOOLS[name];
|
||||
} else {
|
||||
console.warn(`Tool '${name}' not found in registry`);
|
||||
}
|
||||
return tools;
|
||||
}, {} as Record<string, any>);
|
||||
}
|
||||
|
||||
export function getDefaultToolNamesForRole(role: 'orchestrator' | 'executor' | 'planner'): string[] {
|
||||
if (role === 'orchestrator') {
|
||||
return [...ORCHESTRATOR_TOOL_NAMES];
|
||||
}
|
||||
if (role === 'planner') {
|
||||
return [...PLANNER_TOOL_NAMES];
|
||||
}
|
||||
return [...EXECUTOR_TOOL_NAMES];
|
||||
}
|
||||
|
||||
export function getToolsForRole(role: 'orchestrator' | 'executor' | 'planner'): Record<string, any> {
|
||||
const names = getDefaultToolNamesForRole(role);
|
||||
return getHelperTools(names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a tool with given parameters and context
|
||||
*/
|
||||
export async function executeTool(toolId: string, params: any, context: any) {
|
||||
const tool = getTool(toolId);
|
||||
|
||||
if (!tool) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Tool '${toolId}' not found`,
|
||||
data: null
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return await tool.execute(params, context);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : `Tool '${toolId}' execution failed`,
|
||||
data: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export group utilities
|
||||
export { getToolGroup, groupTools, getAllToolsByGroup };
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Node } from '@/types/database';
|
||||
import { SQLiteClient } from '@/services/database/sqlite-client';
|
||||
|
||||
export interface ToolContext {
|
||||
selectedNodes: Node[];
|
||||
database: SQLiteClient;
|
||||
}
|
||||
|
||||
export interface ToolSchema {
|
||||
type: 'function';
|
||||
function: {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: {
|
||||
type: 'object';
|
||||
properties: Record<string, any>;
|
||||
required: string[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
schema: ToolSchema;
|
||||
execute: (params: any, context: ToolContext) => Promise<any>;
|
||||
}
|
||||
Reference in New Issue
Block a user