From cae9f7335c686843cbb4c86a98d2174ca98acdb6 Mon Sep 17 00:00:00 2001
From: cyrus
Date: Tue, 10 Mar 2026 01:12:31 +0530
Subject: [PATCH] remove: deprecated agent system files and types
- Removed unused `agent.ts` system types, agent loop service (`agentLoop.ts`), corresponding UI components (`AgentExecutionPanel.tsx`), and tests (`agentSlice.test.ts`).
- Cleaned up related imports and ensured no functionality depends on the deprecated files.
---
src/components/agent/AgentExecutionPanel.tsx | 236 ---------
src/components/agent/AgentStatusIndicator.tsx | 96 ----
src/components/agent/AgentToggle.tsx | 162 ------
src/components/agent/index.ts | 13 -
src/pages/Conversations.tsx | 124 ++++-
src/services/agentLoop.ts | 388 --------------
src/services/api/inferenceApi.ts | 28 +-
src/store/__tests__/agentSlice.test.ts | 496 ------------------
src/store/agentSlice.ts | 433 ---------------
src/store/index.ts | 9 -
src/store/threadSlice.ts | 46 +-
src/types/agent.ts | 379 -------------
12 files changed, 132 insertions(+), 2278 deletions(-)
delete mode 100644 src/components/agent/AgentExecutionPanel.tsx
delete mode 100644 src/components/agent/AgentStatusIndicator.tsx
delete mode 100644 src/components/agent/AgentToggle.tsx
delete mode 100644 src/components/agent/index.ts
delete mode 100644 src/services/agentLoop.ts
delete mode 100644 src/store/__tests__/agentSlice.test.ts
delete mode 100644 src/store/agentSlice.ts
delete mode 100644 src/types/agent.ts
diff --git a/src/components/agent/AgentExecutionPanel.tsx b/src/components/agent/AgentExecutionPanel.tsx
deleted file mode 100644
index 54ada33a5..000000000
--- a/src/components/agent/AgentExecutionPanel.tsx
+++ /dev/null
@@ -1,236 +0,0 @@
-/**
- * Agent Execution Panel Component
- *
- * Detailed view of agent execution progress, tool executions, and results.
- * Expandable panel that shows real-time execution details.
- */
-
-import { memo, useMemo } from 'react';
-import { useAppSelector } from '../../store/hooks';
-import {
- selectActiveExecutionForThread,
- selectExecutionHistoryForThread,
- selectAgentModeForThread
-} from '../../store/agentSlice';
-import type { AgentToolExecution } from '../../types/agent';
-
-interface AgentExecutionPanelProps {
- threadId: string;
- className?: string;
- maxHeight?: string;
-}
-
-const formatDuration = (ms: number): string => {
- if (ms < 1000) return `${ms}ms`;
- if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
- return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
-};
-
-const getStatusIcon = (status: string) => {
- switch (status) {
- case 'pending':
- return (
-
- );
- case 'running':
- return (
-
- );
- case 'success':
- return (
-
- );
- case 'error':
- return (
-
- );
- default:
- return (
-
- );
- }
-};
-
-const ToolExecutionItem = memo<{ toolExecution: AgentToolExecution }>(({ toolExecution }) => {
- const duration = toolExecution.executionTimeMs || (toolExecution.endTime ? toolExecution.endTime - toolExecution.startTime : null);
-
- return (
-
-
- {getStatusIcon(toolExecution.status)}
-
-
-
-
- {toolExecution.toolName}
-
- {toolExecution.skillId}
-
- {duration && (
-
- {formatDuration(duration)}
-
- )}
-
-
- {/* Arguments */}
- {toolExecution.arguments && (
-
-
Arguments:
-
- {JSON.stringify(JSON.parse(toolExecution.arguments), null, 2)}
-
-
- )}
-
- {/* Result */}
- {toolExecution.result && (
-
-
Result:
-
- {toolExecution.result}
-
-
- )}
-
- {/* Error */}
- {toolExecution.errorMessage && (
-
-
Error:
-
- {toolExecution.errorMessage}
-
-
- )}
-
-
- );
-});
-
-ToolExecutionItem.displayName = 'ToolExecutionItem';
-
-const AgentExecutionPanel = memo(({
- threadId,
- className = '',
- maxHeight = '400px'
-}) => {
- const agentMode = useAppSelector(state => selectAgentModeForThread(state, threadId));
- const activeExecution = useAppSelector(state => selectActiveExecutionForThread(state, threadId));
- const executionHistory = useAppSelector(state => selectExecutionHistoryForThread(state, threadId));
-
- const sortedToolExecutions = useMemo(() => {
- if (!activeExecution) return [];
- return [...activeExecution.toolExecutions].sort((a, b) => a.startTime - b.startTime);
- }, [activeExecution]);
-
- const recentHistory = useMemo(() => {
- return executionHistory.slice(0, 3); // Show last 3 completed executions
- }, [executionHistory]);
-
- if (!agentMode) {
- return null;
- }
-
- return (
-
-
-
Agent Execution Details
-
-
-
- {/* Active Execution */}
- {activeExecution && (
-
-
-
Current Execution
-
- Running for {formatDuration(Date.now() - activeExecution.startTime)}
-
-
-
-
-
Progress:
-
-
-
- {activeExecution.currentIteration}/{activeExecution.maxIterations}
-
-
-
-
- {/* Tool Executions */}
- {sortedToolExecutions.length > 0 && (
-
-
- Tool Executions ({sortedToolExecutions.length}):
-
- {sortedToolExecutions.map(toolExecution => (
-
- ))}
-
- )}
-
- )}
-
- {/* Execution History */}
- {recentHistory.length > 0 && (
-
-
Recent Executions
-
- {recentHistory.map(entry => (
-
-
-
-
- {entry.result.status}
-
-
- {entry.result.toolExecutions.length} tools
-
-
-
- {formatDuration(entry.duration)}
-
-
- ))}
-
-
- )}
-
- {/* Empty State */}
- {!activeExecution && recentHistory.length === 0 && (
-
-
-
No agent executions yet
-
Send a message to start an agent task
-
- )}
-
-
- );
-});
-
-AgentExecutionPanel.displayName = 'AgentExecutionPanel';
-
-export default AgentExecutionPanel;
\ No newline at end of file
diff --git a/src/components/agent/AgentStatusIndicator.tsx b/src/components/agent/AgentStatusIndicator.tsx
deleted file mode 100644
index 87e4d9238..000000000
--- a/src/components/agent/AgentStatusIndicator.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * Agent Status Indicator Component
- *
- * Shows the current status of agent execution within thread UI.
- * Displays real-time agent activity, tool executions, and completion status.
- */
-
-import { memo } from 'react';
-import { useAppSelector } from '../../store/hooks';
-import { selectActiveExecutionForThread, selectAgentModeForThread } from '../../store/agentSlice';
-
-interface AgentStatusIndicatorProps {
- threadId: string;
- className?: string;
-}
-
-const AgentStatusIndicator = memo(({ threadId, className = '' }) => {
- const agentMode = useAppSelector(state => selectAgentModeForThread(state, threadId));
- const activeExecution = useAppSelector(state => selectActiveExecutionForThread(state, threadId));
-
- // Don't render if agent mode is disabled
- if (!agentMode) {
- return null;
- }
-
- // No active execution
- if (!activeExecution) {
- return (
-
- );
- }
-
- const getStatusColor = () => {
- switch (activeExecution.status) {
- case 'initializing':
- return 'bg-amber-500';
- case 'running':
- return 'bg-primary-500 animate-pulse';
- case 'completing':
- return 'bg-sage-500';
- default:
- return 'bg-canvas-400';
- }
- };
-
- const getStatusText = () => {
- switch (activeExecution.status) {
- case 'initializing':
- return 'Starting...';
- case 'running':
- return `Iteration ${activeExecution.currentIteration}/${activeExecution.maxIterations}`;
- case 'completing':
- return 'Finishing...';
- default:
- return 'Agent Active';
- }
- };
-
- const toolCount = activeExecution.toolExecutions.length;
- const runningTools = activeExecution.toolExecutions.filter(t => t.status === 'running').length;
-
- return (
-
- {/* Status indicator */}
-
-
- {/* Tool execution info */}
- {toolCount > 0 && (
-
-
-
{toolCount} tools
- {runningTools > 0 && (
-
• {runningTools} running
- )}
-
- )}
-
- {/* Execution time */}
-
- {Math.floor((Date.now() - activeExecution.startTime) / 1000)}s
-
-
- );
-});
-
-AgentStatusIndicator.displayName = 'AgentStatusIndicator';
-
-export default AgentStatusIndicator;
\ No newline at end of file
diff --git a/src/components/agent/AgentToggle.tsx b/src/components/agent/AgentToggle.tsx
deleted file mode 100644
index 7f648d62a..000000000
--- a/src/components/agent/AgentToggle.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-/**
- * Agent Toggle Component
- *
- * Toggle switch to enable/disable agent mode for a thread.
- * Shows agent status and allows configuration when enabled.
- */
-
-import { memo, useCallback, useState } from 'react';
-import { useAppDispatch, useAppSelector } from '../../store/hooks';
-import {
- selectAgentModeForThread,
- selectAgentConfigForThread,
- selectActiveExecutionForThread,
- setAgentModeForThread,
- loadAgentTools
-} from '../../store/agentSlice';
-
-interface AgentToggleProps {
- threadId: string;
- className?: string;
- size?: 'sm' | 'md' | 'lg';
-}
-
-const AgentToggle = memo(({
- threadId,
- className = '',
- size = 'md'
-}) => {
- const dispatch = useAppDispatch();
- const agentMode = useAppSelector(state => selectAgentModeForThread(state, threadId));
- const agentConfig = useAppSelector(state => selectAgentConfigForThread(state, threadId));
- const activeExecution = useAppSelector(state => selectActiveExecutionForThread(state, threadId));
- const [isLoading, setIsLoading] = useState(false);
-
- const handleToggle = useCallback(async () => {
- if (activeExecution) {
- // Can't disable while agent is running
- return;
- }
-
- setIsLoading(true);
-
- try {
- const newMode = !agentMode;
-
- // Enable agent mode
- if (newMode) {
- // Load tools when enabling agent mode
- await dispatch(loadAgentTools()).unwrap();
- }
-
- dispatch(setAgentModeForThread({
- threadId,
- enabled: newMode
- }));
- } catch (error) {
- console.error('Failed to toggle agent mode:', error);
- } finally {
- setIsLoading(false);
- }
- }, [dispatch, threadId, agentMode, activeExecution]);
-
- const getSizeClasses = () => {
- switch (size) {
- case 'sm':
- return {
- container: 'w-8 h-5',
- toggle: 'w-3 h-3',
- translate: 'translate-x-3'
- };
- case 'lg':
- return {
- container: 'w-12 h-7',
- toggle: 'w-5 h-5',
- translate: 'translate-x-5'
- };
- default: // md
- return {
- container: 'w-10 h-6',
- toggle: 'w-4 h-4',
- translate: 'translate-x-4'
- };
- }
- };
-
- const sizeClasses = getSizeClasses();
- const isDisabled = isLoading || Boolean(activeExecution);
-
- return (
-
- {/* Toggle Switch */}
-
-
- {/* Label and Status */}
-
-
-
- Agent Mode
-
-
- {agentMode && (
-
- Active
-
- )}
-
-
- {/* Configuration hint */}
- {agentMode && !activeExecution && (
-
- {agentConfig.maxIterations ? `Max ${agentConfig.maxIterations} iterations` : 'Default settings'}
- {agentConfig.allowedSkills && agentConfig.allowedSkills.length > 0 &&
- ` • ${agentConfig.allowedSkills.length} skills allowed`
- }
-
- )}
-
- {/* Active execution status */}
- {activeExecution && (
-
- Running iteration {activeExecution.currentIteration}/{activeExecution.maxIterations}
-
- )}
-
-
- );
-});
-
-AgentToggle.displayName = 'AgentToggle';
-
-export default AgentToggle;
\ No newline at end of file
diff --git a/src/components/agent/index.ts b/src/components/agent/index.ts
deleted file mode 100644
index 3305c8fdb..000000000
--- a/src/components/agent/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * Agent Components Export Index
- *
- * Centralized exports for all agent-related UI components.
- */
-
-export { default as AgentStatusIndicator } from './AgentStatusIndicator';
-export { default as AgentToggle } from './AgentToggle';
-export { default as AgentExecutionPanel } from './AgentExecutionPanel';
-
-export type { default as AgentStatusIndicatorProps } from './AgentStatusIndicator';
-export type { default as AgentToggleProps } from './AgentToggle';
-export type { default as AgentExecutionPanelProps } from './AgentExecutionPanel';
\ No newline at end of file
diff --git a/src/pages/Conversations.tsx b/src/pages/Conversations.tsx
index 182642c75..d9f4eb2d6 100644
--- a/src/pages/Conversations.tsx
+++ b/src/pages/Conversations.tsx
@@ -9,10 +9,10 @@ import {
import Markdown from 'react-markdown';
import { useNavigate, useParams } from 'react-router-dom';
-import { inferenceApi, type ModelInfo } from '../services/api/inferenceApi';
+import { inferenceApi, type ModelInfo, type Tool, type ChatMessage } from '../services/api/inferenceApi';
+import { AgentToolRegistry } from '../services/agentToolRegistry';
import { injectAll } from '../lib/ai/injector';
import type { Message } from '../lib/ai/providers/interface';
-import { AgentToggle, AgentStatusIndicator, AgentExecutionPanel } from '../components/agent';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import {
addInferenceResponse,
@@ -278,21 +278,110 @@ const Conversations = () => {
// Continue with original message
}
- const chatMessages = [
+ // Load available tools for transparent tool calling
+ const toolRegistry = AgentToolRegistry.getInstance();
+ let availableTools: Tool[] = [];
+
+ try {
+ const toolSchemas = await toolRegistry.loadToolSchemas();
+ availableTools = toolSchemas.map(schema => ({
+ type: 'function' as const,
+ function: {
+ name: schema.function.name,
+ description: schema.function.description,
+ parameters: schema.function.parameters
+ }
+ }));
+ console.log(`🔧 Loaded ${availableTools.length} tools for transparent execution`);
+ } catch (error) {
+ console.warn('⚠️ Failed to load tools, continuing without tool support:', error);
+ }
+
+ const chatMessages: ChatMessage[] = [
...historySnapshot.map(m => ({
- role: (m.sender === 'user' ? 'user' : 'assistant') as 'user' | 'assistant',
+ role: (m.sender === 'user' ? 'user' : 'assistant') as ChatMessage['role'],
content: m.content,
})),
{ role: 'user' as const, content: processedUserContent },
];
- const response = await inferenceApi.createChatCompletion({
- model: selectedModel,
- messages: chatMessages,
- });
+ // Tool calling loop - continue until no more tool calls needed
+ let currentMessages = [...chatMessages];
+ let finalResponse = '';
+ let iterations = 0;
+ const maxIterations = 10;
- const content = response.choices[0]?.message?.content ?? '';
- dispatch(addInferenceResponse({ content }));
+ while (iterations < maxIterations) {
+ iterations++;
+ console.log(`🔄 Tool calling iteration ${iterations}`);
+
+ const response = await inferenceApi.createChatCompletion({
+ model: selectedModel,
+ messages: currentMessages,
+ tools: availableTools.length > 0 ? availableTools : undefined,
+ tool_choice: availableTools.length > 0 ? 'auto' : undefined,
+ });
+
+ const assistantMessage = response.choices[0]?.message;
+ if (!assistantMessage) {
+ throw new Error('No assistant message in response');
+ }
+
+ // Add assistant message to conversation
+ currentMessages.push({
+ role: 'assistant',
+ content: assistantMessage.content,
+ tool_calls: assistantMessage.tool_calls,
+ });
+
+ // If no tool calls, we're done
+ if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
+ finalResponse = assistantMessage.content || '';
+ break;
+ }
+
+ console.log(`🛠️ Executing ${assistantMessage.tool_calls.length} tool calls`);
+
+ // Execute each tool call
+ for (const toolCall of assistantMessage.tool_calls) {
+ try {
+ const { function: { name: toolName, arguments: toolArgs } } = toolCall;
+
+ // Extract skill ID from tool name (format: skillId_toolName)
+ const underscoreIndex = toolName.lastIndexOf('_');
+ const skillId = underscoreIndex > -1 ? toolName.substring(0, underscoreIndex) : 'unknown';
+
+ console.log(`⚡ Executing tool: ${toolName} with args: ${toolArgs}`);
+
+ const execution = await toolRegistry.executeTool(skillId, toolName, toolArgs);
+
+ // Add tool result to conversation
+ currentMessages.push({
+ role: 'tool',
+ content: execution.result || execution.errorMessage || 'Tool executed',
+ tool_call_id: toolCall.id,
+ });
+
+ console.log(`✅ Tool ${toolName} completed: ${execution.status}`);
+ } catch (error) {
+ console.error(`❌ Tool execution failed for ${toolCall.function.name}:`, error);
+
+ // Add error result to conversation
+ currentMessages.push({
+ role: 'tool',
+ content: `Tool execution failed: ${error}`,
+ tool_call_id: toolCall.id,
+ });
+ }
+ }
+ }
+
+ if (iterations >= maxIterations) {
+ console.warn(`⚠️ Tool calling loop exceeded maximum iterations (${maxIterations})`);
+ finalResponse = finalResponse || 'Task completed with maximum iterations reached.';
+ }
+
+ dispatch(addInferenceResponse({ content: finalResponse }));
} catch (err) {
dispatch(removeOptimisticMessages());
const msg =
@@ -596,9 +685,6 @@ const Conversations = () => {
Created {formatRelativeTime(selectedThread.createdAt)}
-
{/* Messages */}
@@ -743,18 +829,6 @@ const Conversations = () => {
)}
- {/* Agent Status and Execution Panel */}
-
{/* Message Input */}
diff --git a/src/services/agentLoop.ts b/src/services/agentLoop.ts
deleted file mode 100644
index 231f344a8..000000000
--- a/src/services/agentLoop.ts
+++ /dev/null
@@ -1,388 +0,0 @@
-/**
- * Agent Loop Service
- *
- * Orchestrates autonomous agent task execution by:
- * 1. Loading tools from the existing skill system
- * 2. Sending requests to the backend (which proxies to AI providers)
- * 3. Executing tool calls using the skill system
- * 4. Managing conversation state and iteration
- */
-
-import { AgentToolRegistry } from './agentToolRegistry';
-import { apiClient } from './apiClient';
-import type {
- AgentExecutionOptions,
- AgentExecutionResult,
- AgentToolExecution,
- AgentChatRequest,
- AgentChatResponse,
- OpenAIMessage,
- OpenAITool,
- IAgentLoop
-} from '../types/agent';
-
-export class AgentLoop implements IAgentLoop {
- private static instance: AgentLoop;
- private toolRegistry: AgentToolRegistry;
- private activeExecutions = new Map
();
-
- constructor() {
- this.toolRegistry = AgentToolRegistry.getInstance();
- }
-
- static getInstance(): AgentLoop {
- if (!this.instance) {
- this.instance = new AgentLoop();
- }
- return this.instance;
- }
-
- /**
- * Execute an agent task autonomously
- */
- async executeTask(
- userMessage: string,
- threadId: string,
- options: AgentExecutionOptions = {}
- ): Promise {
- const {
- maxIterations = 10,
- timeoutMs = 300000, // 5 minutes
- requireApproval = false,
- allowedSkills,
- blockedTools = [],
- retryFailedTools = false
- } = options;
-
- const executionId = `agent_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
- const abortController = new AbortController();
- this.activeExecutions.set(executionId, abortController);
-
- const startTime = Date.now();
- const toolExecutions: AgentToolExecution[] = [];
- let iterations = 0;
-
- try {
- console.log(`🤖 Starting agent task execution (${executionId})`);
- console.log(`📝 User message: "${userMessage}"`);
- console.log(`⚙️ Options:`, { maxIterations, timeoutMs, allowedSkills, blockedTools });
-
- // Set up timeout
- const timeoutId = setTimeout(() => {
- console.log(`⏰ Agent execution timeout (${timeoutMs}ms)`);
- abortController.abort();
- }, timeoutMs);
-
- try {
- // Load available tools from skill system
- console.log('🔧 Loading available tools from skills...');
- const toolSchemas = await this.toolRegistry.loadToolSchemas();
-
- // Filter tools based on configuration
- const availableTools = this.filterTools(toolSchemas, allowedSkills, blockedTools);
- console.log(`🛠️ Agent has access to ${availableTools.length} tools from ${toolSchemas.length} total`);
-
- // Convert to OpenAI format for backend compatibility
- const tools = availableTools.map(this.convertToOpenAITool);
-
- // Initialize conversation with user message
- const messages: OpenAIMessage[] = [
- {
- role: 'user',
- content: userMessage
- }
- ];
-
- let finalResponse: string | undefined;
-
- // Agent iteration loop
- while (iterations < maxIterations && !abortController.signal.aborted) {
- iterations++;
- console.log(`🔄 Agent iteration ${iterations}/${maxIterations}`);
-
- try {
- // Send request to backend (which proxies to AI provider)
- const request: AgentChatRequest = {
- model: 'gpt-4', // Backend will handle the actual model
- messages: [...messages],
- tools,
- tool_choice: 'auto',
- temperature: 0.7,
- max_tokens: 4096
- };
-
- console.log('📤 Sending request to backend proxy...');
- const response = await apiClient.post(
- `/api/v1/conversations/${threadId}/messages`,
- request,
- {
- signal: abortController.signal
- }
- );
-
- const assistantMessage = response.data.choices[0]?.message;
- if (!assistantMessage) {
- throw new Error('No response from AI provider');
- }
-
- console.log(`📥 Received response: ${assistantMessage.tool_calls?.length || 0} tool calls`);
-
- // Add assistant message to conversation
- messages.push(assistantMessage);
-
- // Check if AI wants to call tools
- if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
- console.log(`🛠️ Executing ${assistantMessage.tool_calls.length} tool calls...`);
-
- // Execute each tool call
- for (const toolCall of assistantMessage.tool_calls) {
- if (abortController.signal.aborted) {
- break;
- }
-
- const execution = await this.executeSingleTool(
- toolCall,
- availableTools,
- requireApproval,
- abortController.signal
- );
-
- toolExecutions.push(execution);
-
- // Add tool result to conversation
- messages.push({
- role: 'tool',
- content: execution.result || execution.errorMessage || 'No result',
- tool_call_id: toolCall.id
- });
-
- console.log(`✅ Tool result added to conversation: ${execution.status}`);
- }
-
- // Continue to next iteration to let AI process tool results
- continue;
- } else {
- // AI provided final response
- finalResponse = assistantMessage.content || '';
- console.log('✅ Agent task completed with final response');
- break;
- }
-
- } catch (error) {
- console.error(`❌ Error in agent iteration ${iterations}:`, error);
-
- if (abortController.signal.aborted) {
- clearTimeout(timeoutId);
- return {
- status: 'timeout',
- executionId,
- iterations,
- toolExecutions,
- executionTime: Date.now() - startTime,
- error: 'Execution timed out'
- };
- }
-
- clearTimeout(timeoutId);
- return {
- status: 'error',
- executionId,
- iterations,
- toolExecutions,
- executionTime: Date.now() - startTime,
- error: error instanceof Error ? error.message : String(error)
- };
- }
- }
-
- clearTimeout(timeoutId);
-
- // Check if we hit max iterations
- if (iterations >= maxIterations && !finalResponse) {
- console.log('⚠️ Agent reached maximum iterations without completion');
- return {
- status: 'max_iterations',
- executionId,
- iterations,
- toolExecutions,
- executionTime: Date.now() - startTime,
- error: 'Maximum iterations reached without completion'
- };
- }
-
- const executionTime = Date.now() - startTime;
- console.log(`🎉 Agent execution completed successfully in ${executionTime}ms`);
- console.log(`📊 Stats: ${iterations} iterations, ${toolExecutions.length} tool executions`);
-
- return {
- status: 'completed',
- executionId,
- finalResponse,
- iterations,
- toolExecutions,
- executionTime,
- metadata: {
- toolsAvailable: availableTools.length,
- skillsInvolved: [...new Set(toolExecutions.map(te => te.skillId))]
- }
- };
-
- } finally {
- clearTimeout(timeoutId);
- }
-
- } catch (error) {
- console.error('❌ Agent execution failed:', error);
-
- return {
- status: 'error',
- executionId,
- iterations,
- toolExecutions,
- executionTime: Date.now() - startTime,
- error: error instanceof Error ? error.message : String(error)
- };
- } finally {
- this.activeExecutions.delete(executionId);
- }
- }
-
- /**
- * Cancel an active agent execution
- */
- cancelExecution(executionId: string): boolean {
- const controller = this.activeExecutions.get(executionId);
- if (controller) {
- controller.abort();
- this.activeExecutions.delete(executionId);
- console.log(`🛑 Cancelled agent execution: ${executionId}`);
- return true;
- }
- return false;
- }
-
- /**
- * Get list of active execution IDs
- */
- getActiveExecutions(): string[] {
- return Array.from(this.activeExecutions.keys());
- }
-
- /**
- * Get execution status (placeholder - would need Redux integration)
- */
- getExecutionStatus(executionId: string): null {
- // This would typically integrate with Redux state
- // For now, just return null
- return null;
- }
-
- // =============================================================================
- // Private Helper Methods
- // =============================================================================
-
- /**
- * Execute a single tool call
- */
- private async executeSingleTool(
- toolCall: any,
- availableTools: any[],
- requireApproval: boolean,
- signal: AbortSignal
- ): Promise {
- const startTime = Date.now();
-
- try {
- // Find the tool and its associated skill
- const toolSchema = availableTools.find(t => t.function.name === toolCall.function.name);
- if (!toolSchema) {
- return {
- id: toolCall.id,
- toolName: toolCall.function.name,
- skillId: 'unknown',
- arguments: toolCall.function.arguments,
- status: 'error',
- startTime,
- endTime: Date.now(),
- errorMessage: `Tool not found: ${toolCall.function.name}`
- };
- }
-
- const skillId = (toolSchema.function as any).skillId;
-
- console.log(`🔧 Executing tool: ${skillId}.${toolCall.function.name}`);
-
- // TODO: Implement approval workflow if requireApproval is true
-
- // Execute the tool using the existing skill system
- const result = await this.toolRegistry.executeTool(
- skillId,
- toolCall.function.name,
- toolCall.function.arguments
- );
-
- console.log(`✅ Tool execution ${result.status}: ${toolCall.function.name}`);
-
- return {
- ...result,
- id: toolCall.id // Use the tool call ID from the AI
- };
-
- } catch (error) {
- const endTime = Date.now();
- console.error(`❌ Tool execution error: ${toolCall.function.name}`, error);
-
- return {
- id: toolCall.id,
- toolName: toolCall.function.name,
- skillId: 'unknown',
- arguments: toolCall.function.arguments,
- status: 'error',
- startTime,
- endTime,
- executionTimeMs: endTime - startTime,
- errorMessage: error instanceof Error ? error.message : String(error)
- };
- }
- }
-
- /**
- * Filter tools based on allowed skills and blocked tools
- */
- private filterTools(
- toolSchemas: any[],
- allowedSkills?: string[],
- blockedTools: string[] = []
- ): any[] {
- return toolSchemas.filter(tool => {
- const skillId = (tool.function as any).skillId;
- const toolName = tool.function.name;
-
- // Check if tool is blocked
- if (blockedTools.includes(toolName)) {
- return false;
- }
-
- // Check if skill is allowed (if allowedSkills is specified)
- if (allowedSkills && allowedSkills.length > 0) {
- return allowedSkills.includes(skillId);
- }
-
- return true;
- });
- }
-
- /**
- * Convert agent tool schema to OpenAI tool format
- */
- private convertToOpenAITool(toolSchema: any): OpenAITool {
- return {
- type: 'function',
- function: {
- name: toolSchema.function.name,
- description: toolSchema.function.description,
- parameters: toolSchema.function.parameters
- }
- };
- }
-}
\ No newline at end of file
diff --git a/src/services/api/inferenceApi.ts b/src/services/api/inferenceApi.ts
index 2c59841cc..6dbf7e466 100644
--- a/src/services/api/inferenceApi.ts
+++ b/src/services/api/inferenceApi.ts
@@ -2,16 +2,40 @@ import { apiClient } from '../apiClient';
// ── Request types ────────────────────────────────────────────────────────────
-export type ChatRole = 'system' | 'user' | 'assistant';
+export type ChatRole = 'system' | 'user' | 'assistant' | 'tool';
+
+export interface ToolCall {
+ id: string;
+ type: 'function';
+ function: {
+ name: string;
+ arguments: string;
+ };
+}
export interface ChatMessage {
role: ChatRole;
- content: string;
+ content: string | null;
+ tool_calls?: ToolCall[];
+ tool_call_id?: string;
+}
+
+export interface ToolFunction {
+ name: string;
+ description: string;
+ parameters: any;
+}
+
+export interface Tool {
+ type: 'function';
+ function: ToolFunction;
}
export interface ChatCompletionRequest {
model: string;
messages: ChatMessage[];
+ tools?: Tool[];
+ tool_choice?: 'auto' | 'none' | { type: 'function'; function: { name: string } };
stream?: boolean;
temperature?: number;
max_tokens?: number;
diff --git a/src/store/__tests__/agentSlice.test.ts b/src/store/__tests__/agentSlice.test.ts
deleted file mode 100644
index 0d4197422..000000000
--- a/src/store/__tests__/agentSlice.test.ts
+++ /dev/null
@@ -1,496 +0,0 @@
-import { describe, test, expect, beforeEach, vi } from 'vitest';
-import { configureStore } from '@reduxjs/toolkit';
-import agentReducer, {
- setAgentMode,
- startAgentExecution,
- updateExecutionProgress,
- completeAgentExecution,
- cancelAgentExecution,
- setToolRegistry,
- clearExecutionHistory,
- executeAgentTask,
- loadAgentTools,
- cancelAgentExecutionThunk,
- type AgentState
-} from '../agentSlice';
-import type {
- AgentExecutionResult,
- AgentToolExecution,
- AgentToolSchema
-} from '../../types/agent';
-
-// Mock dependencies
-vi.mock('../../services/agentLoop');
-vi.mock('../../services/agentToolRegistry');
-
-describe('agentSlice', () => {
- let store: ReturnType;
-
- beforeEach(() => {
- store = configureStore({
- reducer: {
- agent: agentReducer
- }
- });
- });
-
- describe('synchronous actions', () => {
- test('setAgentMode should toggle agent mode', () => {
- expect(store.getState().agent.isAgentMode).toBe(false);
-
- store.dispatch(setAgentMode(true));
- expect(store.getState().agent.isAgentMode).toBe(true);
-
- store.dispatch(setAgentMode(false));
- expect(store.getState().agent.isAgentMode).toBe(false);
- });
-
- test('startAgentExecution should initialize execution state', () => {
- const executionId = 'exec_123';
- const threadId = 'thread_456';
- const userMessage = 'Help me with GitHub issues';
-
- store.dispatch(startAgentExecution({ executionId, threadId, userMessage }));
-
- const state = store.getState().agent;
- expect(state.currentExecution).toEqual({
- id: executionId,
- threadId,
- userMessage,
- status: 'running',
- iterations: 0,
- toolExecutions: [],
- startTime: expect.any(Number),
- executionTime: 0
- });
- expect(state.executionHistory).toHaveLength(1);
- expect(state.executionHistory[0].id).toBe(executionId);
- });
-
- test('updateExecutionProgress should update current execution', () => {
- const executionId = 'exec_123';
- const threadId = 'thread_456';
-
- // Start execution first
- store.dispatch(startAgentExecution({
- executionId,
- threadId,
- userMessage: 'Test'
- }));
-
- const toolExecution: AgentToolExecution = {
- id: 'tool_exec_1',
- toolName: 'list_issues',
- skillId: 'github',
- arguments: '{"owner":"user","repo":"test"}',
- status: 'running',
- startTime: Date.now()
- };
-
- store.dispatch(updateExecutionProgress({
- executionId,
- iteration: 1,
- toolExecution
- }));
-
- const state = store.getState().agent;
- expect(state.currentExecution?.iterations).toBe(1);
- expect(state.currentExecution?.toolExecutions).toHaveLength(1);
- expect(state.currentExecution?.toolExecutions[0]).toEqual(toolExecution);
- });
-
- test('updateExecutionProgress should update existing tool execution', () => {
- const executionId = 'exec_123';
- const threadId = 'thread_456';
-
- // Start execution
- store.dispatch(startAgentExecution({
- executionId,
- threadId,
- userMessage: 'Test'
- }));
-
- const toolExecution: AgentToolExecution = {
- id: 'tool_exec_1',
- toolName: 'list_issues',
- skillId: 'github',
- arguments: '{}',
- status: 'running',
- startTime: Date.now()
- };
-
- // Add tool execution
- store.dispatch(updateExecutionProgress({
- executionId,
- iteration: 1,
- toolExecution
- }));
-
- // Update the same tool execution with completion
- const updatedToolExecution: AgentToolExecution = {
- ...toolExecution,
- status: 'success',
- endTime: Date.now(),
- executionTimeMs: 1500,
- result: '{"issues":[]}'
- };
-
- store.dispatch(updateExecutionProgress({
- executionId,
- iteration: 1,
- toolExecution: updatedToolExecution
- }));
-
- const state = store.getState().agent;
- expect(state.currentExecution?.toolExecutions).toHaveLength(1);
- expect(state.currentExecution?.toolExecutions[0].status).toBe('success');
- expect(state.currentExecution?.toolExecutions[0].result).toBe('{"issues":[]}');
- });
-
- test('completeAgentExecution should finalize execution', () => {
- const executionId = 'exec_123';
- const threadId = 'thread_456';
-
- // Start execution first
- store.dispatch(startAgentExecution({
- executionId,
- threadId,
- userMessage: 'Test'
- }));
-
- const completionData = {
- executionId,
- status: 'completed' as const,
- finalResponse: 'Task completed successfully',
- totalExecutionTime: 5000
- };
-
- store.dispatch(completeAgentExecution(completionData));
-
- const state = store.getState().agent;
- expect(state.currentExecution?.status).toBe('completed');
- expect(state.currentExecution?.finalResponse).toBe('Task completed successfully');
- expect(state.currentExecution?.executionTime).toBe(5000);
- expect(state.lastExecutionId).toBe(executionId);
-
- // Execution should be updated in history
- const historyItem = state.executionHistory.find(item => item.id === executionId);
- expect(historyItem?.status).toBe('completed');
- expect(historyItem?.finalResponse).toBe('Task completed successfully');
- });
-
- test('cancelAgentExecution should cancel current execution', () => {
- const executionId = 'exec_123';
- const threadId = 'thread_456';
-
- // Start execution first
- store.dispatch(startAgentExecution({
- executionId,
- threadId,
- userMessage: 'Test'
- }));
-
- store.dispatch(cancelAgentExecution({
- executionId,
- reason: 'User cancelled'
- }));
-
- const state = store.getState().agent;
- expect(state.currentExecution?.status).toBe('cancelled');
- expect(state.currentExecution?.error).toBe('User cancelled');
- });
-
- test('setToolRegistry should update available tools', () => {
- const mockTools: AgentToolSchema[] = [
- {
- type: "function",
- function: {
- name: "github_list_issues",
- description: "List GitHub issues",
- parameters: {
- type: "object",
- properties: {
- owner: { type: "string" },
- repo: { type: "string" }
- },
- required: ["owner", "repo"]
- }
- }
- }
- ];
-
- store.dispatch(setToolRegistry({
- tools: mockTools,
- lastUpdated: Date.now()
- }));
-
- const state = store.getState().agent;
- expect(state.toolRegistry.tools).toEqual(mockTools);
- expect(state.toolRegistry.lastUpdated).toBeDefined();
- });
-
- test('clearExecutionHistory should reset history', () => {
- const executionId = 'exec_123';
- const threadId = 'thread_456';
-
- // Add some history first
- store.dispatch(startAgentExecution({
- executionId,
- threadId,
- userMessage: 'Test'
- }));
-
- expect(store.getState().agent.executionHistory).toHaveLength(1);
-
- store.dispatch(clearExecutionHistory());
-
- expect(store.getState().agent.executionHistory).toHaveLength(0);
- });
- });
-
- describe('async thunks', () => {
- test('executeAgentTask.pending should set loading state', () => {
- const action = { type: executeAgentTask.pending.type };
- const state = agentReducer(undefined, action);
-
- expect(state.isLoading).toBe(true);
- expect(state.error).toBeNull();
- });
-
- test('executeAgentTask.fulfilled should handle successful execution', () => {
- const mockResult: AgentExecutionResult = {
- status: 'completed',
- executionId: 'exec_123',
- finalResponse: 'Task completed',
- iterations: 2,
- toolExecutions: [],
- executionTime: 3000
- };
-
- const action = {
- type: executeAgentTask.fulfilled.type,
- payload: mockResult
- };
-
- const state = agentReducer(undefined, action);
-
- expect(state.isLoading).toBe(false);
- expect(state.error).toBeNull();
- expect(state.lastExecutionId).toBe('exec_123');
- });
-
- test('executeAgentTask.rejected should handle execution error', () => {
- const action = {
- type: executeAgentTask.rejected.type,
- error: { message: 'Execution failed' }
- };
-
- const state = agentReducer(undefined, action);
-
- expect(state.isLoading).toBe(false);
- expect(state.error).toBe('Execution failed');
- });
-
- test('loadAgentTools.fulfilled should update tool registry', () => {
- const mockTools: AgentToolSchema[] = [
- {
- type: "function",
- function: {
- name: "test_tool",
- description: "Test tool",
- parameters: { type: "object", properties: {} }
- }
- }
- ];
-
- const action = {
- type: loadAgentTools.fulfilled.type,
- payload: mockTools
- };
-
- const state = agentReducer(undefined, action);
-
- expect(state.toolRegistry.tools).toEqual(mockTools);
- expect(state.toolRegistry.isLoaded).toBe(true);
- expect(state.toolRegistry.lastUpdated).toBeDefined();
- });
-
- test('loadAgentTools.rejected should handle tool loading error', () => {
- const action = {
- type: loadAgentTools.rejected.type,
- error: { message: 'Failed to load tools' }
- };
-
- const state = agentReducer(undefined, action);
-
- expect(state.toolRegistry.isLoaded).toBe(false);
- expect(state.toolRegistry.error).toBe('Failed to load tools');
- });
-
- test('cancelAgentExecutionThunk.fulfilled should cancel execution', () => {
- // First set up an execution
- const initialState: AgentState = {
- isAgentMode: false,
- isLoading: false,
- error: null,
- currentExecution: {
- id: 'exec_123',
- threadId: 'thread_456',
- userMessage: 'Test',
- status: 'running',
- iterations: 1,
- toolExecutions: [],
- startTime: Date.now(),
- executionTime: 0
- },
- executionHistory: [{
- id: 'exec_123',
- threadId: 'thread_456',
- userMessage: 'Test',
- status: 'running',
- iterations: 1,
- toolExecutions: [],
- startTime: Date.now(),
- executionTime: 0
- }],
- lastExecutionId: null,
- toolRegistry: {
- tools: [],
- isLoaded: false,
- lastUpdated: null,
- error: null
- },
- configByThreadId: {}
- };
-
- const action = {
- type: cancelAgentExecutionThunk.fulfilled.type,
- payload: { executionId: 'exec_123' }
- };
-
- const state = agentReducer(initialState, action);
-
- expect(state.currentExecution?.status).toBe('cancelled');
- expect(state.executionHistory[0].status).toBe('cancelled');
- });
- });
-
- describe('selectors and derived state', () => {
- test('should maintain execution history chronologically', () => {
- const execution1 = {
- executionId: 'exec_1',
- threadId: 'thread_1',
- userMessage: 'First task'
- };
-
- const execution2 = {
- executionId: 'exec_2',
- threadId: 'thread_1',
- userMessage: 'Second task'
- };
-
- store.dispatch(startAgentExecution(execution1));
- store.dispatch(startAgentExecution(execution2));
-
- const state = store.getState().agent;
- expect(state.executionHistory).toHaveLength(2);
- expect(state.executionHistory[0].id).toBe('exec_1');
- expect(state.executionHistory[1].id).toBe('exec_2');
- });
-
- test('should track tool execution statistics', () => {
- const executionId = 'exec_123';
-
- store.dispatch(startAgentExecution({
- executionId,
- threadId: 'thread_1',
- userMessage: 'Test'
- }));
-
- // Add multiple tool executions
- const toolExecution1: AgentToolExecution = {
- id: 'tool_1',
- toolName: 'list_issues',
- skillId: 'github',
- arguments: '{}',
- status: 'success',
- startTime: Date.now() - 2000,
- endTime: Date.now() - 1000,
- executionTimeMs: 1000
- };
-
- const toolExecution2: AgentToolExecution = {
- id: 'tool_2',
- toolName: 'create_page',
- skillId: 'notion',
- arguments: '{}',
- status: 'success',
- startTime: Date.now() - 1000,
- endTime: Date.now(),
- executionTimeMs: 1000
- };
-
- store.dispatch(updateExecutionProgress({
- executionId,
- iteration: 1,
- toolExecution: toolExecution1
- }));
-
- store.dispatch(updateExecutionProgress({
- executionId,
- iteration: 2,
- toolExecution: toolExecution2
- }));
-
- const state = store.getState().agent;
- expect(state.currentExecution?.toolExecutions).toHaveLength(2);
- expect(state.currentExecution?.iterations).toBe(2);
- });
- });
-
- describe('error handling', () => {
- test('should handle invalid execution updates gracefully', () => {
- // Try to update non-existent execution
- store.dispatch(updateExecutionProgress({
- executionId: 'non_existent',
- iteration: 1,
- toolExecution: {
- id: 'tool_1',
- toolName: 'test',
- skillId: 'test',
- arguments: '{}',
- status: 'running',
- startTime: Date.now()
- }
- }));
-
- // Should not crash and current execution should remain null
- const state = store.getState().agent;
- expect(state.currentExecution).toBeNull();
- });
-
- test('should handle completion of non-existent execution gracefully', () => {
- store.dispatch(completeAgentExecution({
- executionId: 'non_existent',
- status: 'completed',
- finalResponse: 'Done',
- totalExecutionTime: 1000
- }));
-
- // Should not crash
- const state = store.getState().agent;
- expect(state.currentExecution).toBeNull();
- });
- });
-
- describe('thread-specific configuration', () => {
- test('should store and retrieve thread-specific config', () => {
- const initialState = store.getState().agent;
- expect(initialState.configByThreadId).toEqual({});
-
- // Test that the structure exists for future configuration
- // Note: actual config setting would require a specific action
- expect(typeof initialState.configByThreadId).toBe('object');
- });
- });
-});
\ No newline at end of file
diff --git a/src/store/agentSlice.ts b/src/store/agentSlice.ts
deleted file mode 100644
index 775faa5e8..000000000
--- a/src/store/agentSlice.ts
+++ /dev/null
@@ -1,433 +0,0 @@
-/**
- * Agent Redux slice for managing agent execution state
- *
- * Extends AlphaHuman's existing Redux pattern to handle agent task execution,
- * tool executions, and agent configuration per thread.
- */
-
-import { createSlice, createAsyncThunk, type PayloadAction } from '@reduxjs/toolkit';
-
-import { AgentLoop } from '../services/agentLoop';
-import { AgentToolRegistry } from '../services/agentToolRegistry';
-import type {
- AgentState,
- AgentExecution,
- AgentExecutionResult,
- AgentExecutionOptions,
- AgentExecutionHistoryEntry,
- AgentToolExecution,
- AgentToolSchema
-} from '../types/agent';
-
-// =============================================================================
-// Async Thunks
-// =============================================================================
-
-/**
- * Execute an agent task autonomously
- */
-export const executeAgentTask = createAsyncThunk(
- 'agent/executeTask',
- async (
- params: {
- userMessage: string;
- threadId: string;
- options?: AgentExecutionOptions;
- },
- { getState, rejectWithValue }
- ) => {
- try {
- const agentLoop = AgentLoop.getInstance();
- const result = await agentLoop.executeTask(
- params.userMessage,
- params.threadId,
- params.options
- );
-
- return {
- threadId: params.threadId,
- userMessage: params.userMessage,
- result,
- timestamp: Date.now()
- };
- } catch (error) {
- return rejectWithValue(
- error instanceof Error ? error.message : String(error)
- );
- }
- }
-);
-
-/**
- * Load available tools from the skill system
- */
-export const loadAgentTools = createAsyncThunk(
- 'agent/loadTools',
- async (forceReload = false, { rejectWithValue }) => {
- try {
- const registry = AgentToolRegistry.getInstance();
- const tools = await registry.loadToolSchemas(forceReload);
- return tools;
- } catch (error) {
- return rejectWithValue(
- error instanceof Error ? error.message : String(error)
- );
- }
- }
-);
-
-/**
- * Cancel an active agent execution
- */
-export const cancelAgentExecution = createAsyncThunk(
- 'agent/cancelExecution',
- async (executionId: string, { rejectWithValue }) => {
- try {
- const agentLoop = AgentLoop.getInstance();
- const cancelled = agentLoop.cancelExecution(executionId);
-
- if (!cancelled) {
- throw new Error('Execution not found or already completed');
- }
-
- return executionId;
- } catch (error) {
- return rejectWithValue(
- error instanceof Error ? error.message : String(error)
- );
- }
- }
-);
-
-// =============================================================================
-// Initial State
-// =============================================================================
-
-const initialState: AgentState = {
- agentModeByThreadId: {},
- activeExecutions: {},
- executionHistory: [],
- configByThreadId: {},
- toolRegistry: {
- tools: [],
- lastUpdated: 0,
- loading: false
- },
- ui: {
- showExecutionDetails: {},
- selectedExecution: undefined
- }
-};
-
-// =============================================================================
-// Slice Definition
-// =============================================================================
-
-const agentSlice = createSlice({
- name: 'agent',
- initialState,
- reducers: {
- // Agent mode management
- setAgentModeForThread: (
- state,
- action: PayloadAction<{ threadId: string; enabled: boolean }>
- ) => {
- const { threadId, enabled } = action.payload;
- state.agentModeByThreadId[threadId] = enabled;
- },
-
- // Agent configuration management
- setAgentConfigForThread: (
- state,
- action: PayloadAction<{ threadId: string; config: AgentExecutionOptions }>
- ) => {
- const { threadId, config } = action.payload;
- state.configByThreadId[threadId] = config;
- },
-
- // UI state management
- toggleExecutionDetails: (
- state,
- action: PayloadAction<{ executionId: string }>
- ) => {
- const { executionId } = action.payload;
- state.ui.showExecutionDetails[executionId] = !state.ui.showExecutionDetails[executionId];
- },
-
- setSelectedExecution: (
- state,
- action: PayloadAction
- ) => {
- state.ui.selectedExecution = action.payload;
- },
-
- // Tool registry cache management
- clearToolRegistry: (state) => {
- state.toolRegistry = {
- tools: [],
- lastUpdated: 0,
- loading: false
- };
- },
-
- // Execution tracking (for real-time updates)
- addActiveExecution: (
- state,
- action: PayloadAction
- ) => {
- const execution = action.payload;
- state.activeExecutions[execution.id] = execution;
- },
-
- updateActiveExecution: (
- state,
- action: PayloadAction & { id: string }>
- ) => {
- const { id, ...updates } = action.payload;
- if (state.activeExecutions[id]) {
- Object.assign(state.activeExecutions[id], updates);
- }
- },
-
- removeActiveExecution: (
- state,
- action: PayloadAction
- ) => {
- const executionId = action.payload;
- delete state.activeExecutions[executionId];
- },
-
- // Tool execution updates
- addToolExecution: (
- state,
- action: PayloadAction<{ executionId: string; toolExecution: AgentToolExecution }>
- ) => {
- const { executionId, toolExecution } = action.payload;
- if (state.activeExecutions[executionId]) {
- state.activeExecutions[executionId].toolExecutions.push(toolExecution);
- state.activeExecutions[executionId].lastUpdate = Date.now();
- }
- },
-
- updateToolExecution: (
- state,
- action: PayloadAction<{
- executionId: string;
- toolExecutionId: string;
- updates: Partial;
- }>
- ) => {
- const { executionId, toolExecutionId, updates } = action.payload;
- const execution = state.activeExecutions[executionId];
-
- if (execution) {
- const toolExecution = execution.toolExecutions.find(te => te.id === toolExecutionId);
- if (toolExecution) {
- Object.assign(toolExecution, updates);
- execution.lastUpdate = Date.now();
- }
- }
- },
-
- // Execution history management
- addExecutionToHistory: (
- state,
- action: PayloadAction
- ) => {
- state.executionHistory.unshift(action.payload);
-
- // Keep only last 100 executions
- if (state.executionHistory.length > 100) {
- state.executionHistory = state.executionHistory.slice(0, 100);
- }
- },
-
- clearExecutionHistory: (state) => {
- state.executionHistory = [];
- }
- },
-
- extraReducers: (builder) => {
- // Execute agent task
- builder
- .addCase(executeAgentTask.pending, (state, action) => {
- const { userMessage, threadId } = action.meta.arg;
- const executionId = `agent_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
-
- const execution: AgentExecution = {
- id: executionId,
- threadId,
- userMessage,
- status: 'initializing',
- currentIteration: 0,
- maxIterations: action.meta.arg.options?.maxIterations || 10,
- toolExecutions: [],
- startTime: Date.now(),
- lastUpdate: Date.now()
- };
-
- state.activeExecutions[executionId] = execution;
- })
- .addCase(executeAgentTask.fulfilled, (state, action) => {
- const { threadId, userMessage, result, timestamp } = action.payload;
-
- // Find the execution by thread and message
- const execution = Object.values(state.activeExecutions).find(
- exec => exec.threadId === threadId && exec.userMessage === userMessage
- );
-
- if (execution) {
- // Move from active to history
- const historyEntry: AgentExecutionHistoryEntry = {
- executionId: execution.id,
- threadId,
- userMessage,
- result,
- timestamp,
- duration: Date.now() - execution.startTime
- };
-
- state.executionHistory.unshift(historyEntry);
- delete state.activeExecutions[execution.id];
-
- // Keep only last 100 executions
- if (state.executionHistory.length > 100) {
- state.executionHistory = state.executionHistory.slice(0, 100);
- }
- }
- })
- .addCase(executeAgentTask.rejected, (state, action) => {
- // Remove failed execution from active list
- const rejectedExecution = Object.values(state.activeExecutions).find(
- exec => exec.userMessage === action.meta.arg.userMessage
- );
-
- if (rejectedExecution) {
- delete state.activeExecutions[rejectedExecution.id];
- }
- });
-
- // Load agent tools
- builder
- .addCase(loadAgentTools.pending, (state) => {
- state.toolRegistry.loading = true;
- })
- .addCase(loadAgentTools.fulfilled, (state, action) => {
- state.toolRegistry.tools = action.payload;
- state.toolRegistry.lastUpdated = Date.now();
- state.toolRegistry.loading = false;
- state.toolRegistry.error = undefined;
- })
- .addCase(loadAgentTools.rejected, (state, action) => {
- state.toolRegistry.loading = false;
- state.toolRegistry.error = action.payload as string;
- });
-
- // Cancel agent execution
- builder
- .addCase(cancelAgentExecution.fulfilled, (state, action) => {
- const executionId = action.payload;
- if (state.activeExecutions[executionId]) {
- state.activeExecutions[executionId].status = 'completing';
- }
- });
- }
-});
-
-// =============================================================================
-// Actions Export
-// =============================================================================
-
-export const {
- setAgentModeForThread,
- setAgentConfigForThread,
- toggleExecutionDetails,
- setSelectedExecution,
- clearToolRegistry,
- addActiveExecution,
- updateActiveExecution,
- removeActiveExecution,
- addToolExecution,
- updateToolExecution,
- addExecutionToHistory,
- clearExecutionHistory
-} = agentSlice.actions;
-
-// =============================================================================
-// Selectors
-// =============================================================================
-
-export const selectAgentModeForThread = (state: { agent: AgentState }, threadId: string) =>
- state.agent.agentModeByThreadId[threadId] || false;
-
-export const selectAgentConfigForThread = (state: { agent: AgentState }, threadId: string) =>
- state.agent.configByThreadId[threadId] || {};
-
-export const selectActiveExecutions = (state: { agent: AgentState }) =>
- Object.values(state.agent.activeExecutions);
-
-export const selectActiveExecutionForThread = (state: { agent: AgentState }, threadId: string) =>
- Object.values(state.agent.activeExecutions).find(exec => exec.threadId === threadId);
-
-export const selectExecutionHistory = (state: { agent: AgentState }) =>
- state.agent.executionHistory;
-
-export const selectExecutionHistoryForThread = (state: { agent: AgentState }, threadId: string) =>
- state.agent.executionHistory.filter(entry => entry.threadId === threadId);
-
-export const selectToolRegistry = (state: { agent: AgentState }) =>
- state.agent.toolRegistry;
-
-export const selectAvailableTools = (state: { agent: AgentState }) =>
- state.agent.toolRegistry.tools;
-
-export const selectToolsByCategory = (state: { agent: AgentState }) => {
- const toolsBySkill: Record = {};
-
- for (const tool of state.agent.toolRegistry.tools) {
- const skillId = (tool.function as any).skillId || 'unknown';
- if (!toolsBySkill[skillId]) {
- toolsBySkill[skillId] = [];
- }
- toolsBySkill[skillId].push(tool);
- }
-
- return toolsBySkill;
-};
-
-export const selectToolStats = (state: { agent: AgentState }) => {
- const tools = state.agent.toolRegistry.tools;
- const skillIds = new Set();
- const categories: Record = {};
-
- for (const tool of tools) {
- const skillId = (tool.function as any).skillId || 'unknown';
- skillIds.add(skillId);
-
- // Categorize by skill type
- let category = 'Other';
- if (skillId.includes('github') || skillId.includes('git')) category = 'GitHub';
- else if (skillId.includes('notion')) category = 'Notion';
- else if (skillId.includes('telegram') || skillId.includes('tg')) category = 'Telegram';
- else if (skillId.includes('email') || skillId.includes('gmail')) category = 'Email';
- else if (skillId.includes('calendar')) category = 'Calendar';
- else if (skillId.includes('slack')) category = 'Slack';
-
- categories[category] = (categories[category] || 0) + 1;
- }
-
- return {
- totalTools: tools.length,
- skillCount: skillIds.size,
- categories
- };
-};
-
-export const selectAgentUIState = (state: { agent: AgentState }) =>
- state.agent.ui;
-
-// =============================================================================
-// Reducer Export
-// =============================================================================
-
-export default agentSlice.reducer;
\ No newline at end of file
diff --git a/src/store/index.ts b/src/store/index.ts
index 3d203447c..6e659e00b 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -15,7 +15,6 @@ import storage from 'redux-persist/lib/storage';
import { setStoreForApiClient } from '../services/apiClient';
import { IS_DEV } from '../utils/config';
import { storeSession } from '../utils/tauriCommands';
-import agentReducer from './agentSlice';
import aiReducer from './aiSlice';
import authReducer, { setOnboardedForUser, setToken } from './authSlice';
import daemonReducer from './daemonSlice';
@@ -54,18 +53,11 @@ const threadPersistConfig = {
whitelist: ['panelWidth', 'lastViewedAt', 'threads', 'messagesByThreadId', 'selectedThreadId'],
};
-// Persist config for agent state (execution history, config, and agent mode)
-const agentPersistConfig = {
- key: 'agent',
- storage,
- whitelist: ['agentModeByThreadId', 'executionHistory', 'configByThreadId'],
-};
const persistedAuthReducer = persistReducer(authPersistConfig, authReducer);
const persistedAiReducer = persistReducer(aiPersistConfig, aiReducer);
const persistedSkillsReducer = persistReducer(skillsPersistConfig, skillsReducer);
const persistedThreadReducer = persistReducer(threadPersistConfig, threadReducer);
-const persistedAgentReducer = persistReducer(agentPersistConfig, agentReducer);
/**
* Middleware that syncs the JWT token to the Rust SESSION_SERVICE whenever
@@ -111,7 +103,6 @@ export const store = configureStore({
thread: persistedThreadReducer,
invite: inviteReducer,
notion: notionReducer,
- agent: persistedAgentReducer,
},
middleware: getDefaultMiddleware => {
const middleware = getDefaultMiddleware({
diff --git a/src/store/threadSlice.ts b/src/store/threadSlice.ts
index 425bd0b7c..85d91acef 100644
--- a/src/store/threadSlice.ts
+++ b/src/store/threadSlice.ts
@@ -4,7 +4,6 @@ import { threadApi } from '../services/api/threadApi';
import type { Thread, ThreadMessage } from '../types/thread';
import { injectAll } from '../lib/ai/injector';
import type { Message } from '../lib/ai/providers/interface';
-import { executeAgentTask, selectAgentModeForThread } from './agentSlice';
import type { RootState } from './index';
interface ThreadState {
@@ -124,39 +123,13 @@ export const sendMessage = createAsyncThunk(
// Continue with original message
}
- // 3. Check if agent mode is enabled for this thread
- const state = getState() as RootState;
- const agentMode = selectAgentModeForThread(state, threadId);
+ // 3. Send to API with processed message (disable injection in threadApi to avoid double injection)
+ const data = await threadApi.sendMessage(processedMessage, threadId, { injectSoul: false });
- if (agentMode) {
- // Execute agent task instead of sending to inference API
- console.log('🤖 Agent mode enabled - executing agent task');
+ // 4. For now, we'll handle AI response via the existing inference API
+ // The AI response will be added separately via addInferenceResponse
- const agentResult = await dispatch(executeAgentTask({
- userMessage: message,
- threadId,
- options: state.agent.configByThreadId[threadId] || {}
- })).unwrap();
-
- // Add the agent's final response as an AI message with execution metadata
- if (agentResult.result.finalResponse) {
- dispatch(addInferenceResponse({
- content: agentResult.result.finalResponse,
- agentExecutionId: agentResult.result.executionId,
- toolExecutions: agentResult.result.toolExecutions
- }));
- }
-
- return agentResult;
- } else {
- // 4. Send to API with processed message (disable injection in threadApi to avoid double injection)
- const data = await threadApi.sendMessage(processedMessage, threadId, { injectSoul: false });
-
- // 5. For now, we'll handle AI response via the existing inference API
- // The AI response will be added separately via addInferenceResponse
-
- return data;
- }
+ return data;
} catch (error) {
// Remove optimistic user message on failure
const state = (getState() as { thread: ThreadState }).thread;
@@ -228,17 +201,12 @@ const threadSlice = createSlice({
createdAt: new Date().toISOString(),
});
},
- addInferenceResponse: (state, action: { payload: { content: string; agentExecutionId?: string; toolExecutions?: any[] } }) => {
+ addInferenceResponse: (state, action: { payload: { content: string } }) => {
const aiMessage: ThreadMessage = {
id: `inference-${Date.now()}`,
content: action.payload.content,
type: 'text',
- extraMetadata: {
- ...(action.payload.agentExecutionId && {
- agentExecutionId: action.payload.agentExecutionId,
- toolExecutions: action.payload.toolExecutions || []
- })
- },
+ extraMetadata: {},
sender: 'agent',
createdAt: new Date().toISOString(),
};
diff --git a/src/types/agent.ts b/src/types/agent.ts
deleted file mode 100644
index af3848355..000000000
--- a/src/types/agent.ts
+++ /dev/null
@@ -1,379 +0,0 @@
-/**
- * Agent system types for AlphaHuman.
- * Built on top of the existing skill system infrastructure.
- */
-
-import type { SkillToolDefinition } from '../lib/skills/types';
-import type { ThreadMessage, Thread } from './thread';
-
-// =============================================================================
-// Agent Tool Types (extends skill tools)
-// =============================================================================
-
-/**
- * Agent tool schema compatible with OpenAI function calling format
- * and the existing skill tool system
- */
-export interface AgentToolSchema {
- type: 'function';
- function: {
- name: string;
- description: string;
- parameters: {
- type: 'object';
- properties: Record;
- required?: string[];
- };
- };
-}
-
-export interface AgentToolParameter {
- type: 'string' | 'number' | 'boolean' | 'array' | 'object';
- description?: string;
- enum?: string[];
- items?: AgentToolParameter;
- properties?: Record;
- required?: string[];
- default?: any;
- minimum?: number;
- maximum?: number;
- pattern?: string;
-}
-
-/**
- * Tool execution tracking for agent conversations
- */
-export interface AgentToolExecution {
- id: string;
- toolName: string;
- skillId: string; // Which skill provides this tool
- arguments: string; // JSON string
- result?: string;
- status: AgentToolExecutionStatus;
- startTime: number;
- endTime?: number;
- executionTimeMs?: number;
- errorMessage?: string;
- metadata?: {
- retryCount?: number;
- approvalRequired?: boolean;
- approvalGranted?: boolean;
- };
-}
-
-export type AgentToolExecutionStatus =
- | 'pending'
- | 'running'
- | 'success'
- | 'error'
- | 'cancelled'
- | 'timeout';
-
-// =============================================================================
-// Agent Execution Types
-// =============================================================================
-
-/**
- * Configuration options for agent task execution
- */
-export interface AgentExecutionOptions {
- maxIterations?: number;
- timeoutMs?: number;
- requireApproval?: boolean;
- allowedSkills?: string[]; // Skill IDs that are allowed to execute
- blockedTools?: string[]; // Specific tools that are blocked
- retryFailedTools?: boolean;
-}
-
-/**
- * Result of an agent task execution
- */
-export interface AgentExecutionResult {
- status: AgentExecutionStatus;
- executionId: string;
- finalResponse?: string;
- iterations: number;
- toolExecutions: AgentToolExecution[];
- executionTime: number;
- error?: string;
- metadata?: {
- tokensUsed?: number;
- apiCalls?: number;
- toolsAvailable?: number;
- skillsInvolved?: string[];
- };
-}
-
-export type AgentExecutionStatus =
- | 'completed'
- | 'timeout'
- | 'error'
- | 'max_iterations'
- | 'cancelled'
- | 'blocked';
-
-/**
- * Active agent execution tracking
- */
-export interface AgentExecution {
- id: string;
- threadId: string;
- userMessage: string;
- status: 'initializing' | 'running' | 'completing';
- currentIteration: number;
- maxIterations: number;
- toolExecutions: AgentToolExecution[];
- startTime: number;
- lastUpdate: number;
- abortController?: AbortController;
-}
-
-// =============================================================================
-// OpenAI API Compatibility Types
-// =============================================================================
-
-/**
- * OpenAI-compatible message format for backend communication
- */
-export interface OpenAIMessage {
- role: 'system' | 'user' | 'assistant' | 'tool';
- content: string | null;
- name?: string;
- tool_calls?: OpenAIToolCall[];
- tool_call_id?: string;
-}
-
-export interface OpenAIToolCall {
- id: string;
- type: 'function';
- function: {
- name: string;
- arguments: string; // JSON string
- };
-}
-
-export interface OpenAITool {
- type: 'function';
- function: {
- name: string;
- description: string;
- parameters: any; // JSON Schema
- };
-}
-
-/**
- * Chat completion request sent to backend
- */
-export interface AgentChatRequest {
- model: string;
- messages: OpenAIMessage[];
- tools?: OpenAITool[];
- tool_choice?: 'auto' | 'none' | 'required';
- temperature?: number;
- max_tokens?: number;
-}
-
-/**
- * Chat completion response from backend
- */
-export interface AgentChatResponse {
- id: string;
- object: 'chat.completion';
- created: number;
- model: string;
- choices: AgentChatChoice[];
- usage?: {
- prompt_tokens: number;
- completion_tokens: number;
- total_tokens: number;
- };
-}
-
-export interface AgentChatChoice {
- index: number;
- message: OpenAIMessage;
- finish_reason: 'stop' | 'length' | 'tool_calls' | 'content_filter';
-}
-
-// =============================================================================
-// Thread System Integration
-// =============================================================================
-
-/**
- * Enhanced thread message with agent execution metadata
- */
-export interface AgentThreadMessage extends ThreadMessage {
- // Existing ThreadMessage fields remain the same
- // Enhanced extraMetadata for agent tracking
- extraMetadata: ThreadMessage['extraMetadata'] & {
- agentExecutionId?: string;
- toolExecutions?: AgentToolExecution[];
- iterationNumber?: number;
- agentStatus?: AgentExecutionStatus;
- };
-}
-
-/**
- * Enhanced thread with agent mode capability
- */
-export interface AgentThread extends Thread {
- // Existing Thread fields remain the same
- // Additional agent-specific metadata
- agentMode?: boolean;
- lastAgentExecution?: string;
- agentConfig?: AgentExecutionOptions;
-}
-
-// =============================================================================
-// Redux State Types
-// =============================================================================
-
-/**
- * Agent Redux state that integrates with existing skill system
- */
-export interface AgentState {
- // Agent mode enabled per thread
- agentModeByThreadId: Record;
-
- // Active agent executions
- activeExecutions: Record;
-
- // Agent execution history (persisted)
- executionHistory: AgentExecutionHistoryEntry[];
-
- // Agent configuration per thread (persisted)
- configByThreadId: Record;
-
- // Tool registry cache (derived from skills)
- toolRegistry: {
- tools: AgentToolSchema[];
- lastUpdated: number;
- loading: boolean;
- error?: string;
- };
-
- // UI state (not persisted)
- ui: {
- showExecutionDetails: Record;
- selectedExecution?: string;
- };
-}
-
-export interface AgentExecutionHistoryEntry {
- executionId: string;
- threadId: string;
- userMessage: string;
- result: AgentExecutionResult;
- timestamp: number;
- duration: number;
-}
-
-// =============================================================================
-// Service Interface Types
-// =============================================================================
-
-/**
- * Tool registry service interface
- */
-export interface IAgentToolRegistry {
- loadToolSchemas(forceReload?: boolean): Promise;
- executeTool(skillId: string, toolName: string, toolArguments: string): Promise;
- getToolByName(toolName: string): AgentToolSchema | undefined;
- getAllTools(): AgentToolSchema[];
- getToolsBySkill(): Record;
-}
-
-/**
- * Agent loop service interface
- */
-export interface IAgentLoop {
- executeTask(
- userMessage: string,
- threadId: string,
- options?: AgentExecutionOptions
- ): Promise;
-
- cancelExecution(executionId: string): boolean;
- getActiveExecutions(): string[];
- getExecutionStatus(executionId: string): AgentExecution | null;
-}
-
-// =============================================================================
-// Event Types
-// =============================================================================
-
-/**
- * Agent execution events for real-time UI updates
- */
-export type AgentEvent =
- | AgentExecutionStartedEvent
- | AgentIterationStartedEvent
- | AgentToolExecutionStartedEvent
- | AgentToolExecutionCompletedEvent
- | AgentExecutionCompletedEvent
- | AgentExecutionErrorEvent;
-
-export interface AgentExecutionStartedEvent {
- type: 'AGENT_EXECUTION_STARTED';
- executionId: string;
- threadId: string;
- userMessage: string;
- timestamp: number;
-}
-
-export interface AgentIterationStartedEvent {
- type: 'AGENT_ITERATION_STARTED';
- executionId: string;
- iteration: number;
- timestamp: number;
-}
-
-export interface AgentToolExecutionStartedEvent {
- type: 'AGENT_TOOL_EXECUTION_STARTED';
- executionId: string;
- toolExecution: AgentToolExecution;
- timestamp: number;
-}
-
-export interface AgentToolExecutionCompletedEvent {
- type: 'AGENT_TOOL_EXECUTION_COMPLETED';
- executionId: string;
- toolExecution: AgentToolExecution;
- timestamp: number;
-}
-
-export interface AgentExecutionCompletedEvent {
- type: 'AGENT_EXECUTION_COMPLETED';
- executionId: string;
- result: AgentExecutionResult;
- timestamp: number;
-}
-
-export interface AgentExecutionErrorEvent {
- type: 'AGENT_EXECUTION_ERROR';
- executionId: string;
- error: AgentError;
- timestamp: number;
-}
-
-// =============================================================================
-// Error Types
-// =============================================================================
-
-export interface AgentError extends Error {
- type: AgentErrorType;
- code?: string;
- details?: Record;
- retryable?: boolean;
-}
-
-export type AgentErrorType =
- | 'TOOL_EXECUTION_ERROR'
- | 'TOOL_NOT_FOUND'
- | 'SKILL_NOT_AVAILABLE'
- | 'AGENT_TIMEOUT'
- | 'MAX_ITERATIONS_EXCEEDED'
- | 'API_ERROR'
- | 'VALIDATION_ERROR'
- | 'NETWORK_ERROR'
- | 'UNKNOWN_ERROR';
\ No newline at end of file