diff --git a/backend/agent/api.py b/backend/agent/api.py index d68331fc..55cd7c67 100644 --- a/backend/agent/api.py +++ b/backend/agent/api.py @@ -72,6 +72,11 @@ class AgentResponse(BaseModel): created_at: str updated_at: str +class ThreadAgentResponse(BaseModel): + agent: Optional[AgentResponse] + source: str # "thread", "default", "none", "missing" + message: str + def initialize( _thread_manager: ThreadManager, _db: DBConnection, @@ -401,6 +406,78 @@ async def get_agent_run(agent_run_id: str, user_id: str = Depends(get_current_us "error": agent_run_data['error'] } +@router.get("/thread/{thread_id}/agent", response_model=ThreadAgentResponse) +async def get_thread_agent(thread_id: str, user_id: str = Depends(get_current_user_id_from_jwt)): + """Get the agent details for a specific thread.""" + logger.info(f"Fetching agent details for thread: {thread_id}") + client = await db.client + + try: + # Verify thread access and get thread data including agent_id + await verify_thread_access(client, thread_id, user_id) + thread_result = await client.table('threads').select('agent_id', 'account_id').eq('thread_id', thread_id).execute() + + if not thread_result.data: + raise HTTPException(status_code=404, detail="Thread not found") + + thread_data = thread_result.data[0] + thread_agent_id = thread_data.get('agent_id') + account_id = thread_data.get('account_id') + + # If no agent_id is set in the thread, try to get the default agent + effective_agent_id = thread_agent_id + agent_source = "thread" + + if not effective_agent_id: + # No agent set in thread, get default agent for the account + default_agent_result = await client.table('agents').select('agent_id').eq('account_id', account_id).eq('is_default', True).execute() + if default_agent_result.data: + effective_agent_id = default_agent_result.data[0]['agent_id'] + agent_source = "default" + else: + # No default agent found + return { + "agent": None, + "source": "none", + "message": "No agent configured for this thread" + } + + # Fetch the agent details + agent_result = await client.table('agents').select('*').eq('agent_id', effective_agent_id).eq('account_id', account_id).execute() + + if not agent_result.data: + # Agent was deleted or doesn't exist + return { + "agent": None, + "source": "missing", + "message": f"Agent {effective_agent_id} not found or was deleted" + } + + agent_data = agent_result.data[0] + + return { + "agent": AgentResponse( + agent_id=agent_data['agent_id'], + account_id=agent_data['account_id'], + name=agent_data['name'], + description=agent_data.get('description'), + system_prompt=agent_data['system_prompt'], + configured_mcps=agent_data.get('configured_mcps', []), + agentpress_tools=agent_data.get('agentpress_tools', {}), + is_default=agent_data.get('is_default', False), + created_at=agent_data['created_at'], + updated_at=agent_data['updated_at'] + ), + "source": agent_source, + "message": f"Using {agent_source} agent: {agent_data['name']}" + } + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error fetching agent for thread {thread_id}: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to fetch thread agent: {str(e)}") + @router.get("/agent-run/{agent_run_id}/stream") async def stream_agent_run( agent_run_id: str, diff --git a/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx b/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx index c1030054..483c24cd 100644 --- a/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx +++ b/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx @@ -23,7 +23,6 @@ import { } from '@/lib/api'; import { toast } from 'sonner'; import { ChatInput } from '@/components/thread/chat-input/chat-input'; -import { AgentSelector } from '@/components/thread/chat-input/agent-selector'; import { FileViewerModal } from '@/components/thread/file-viewer-modal'; import { SiteHeader } from '@/components/thread/thread-site-header'; import { @@ -47,6 +46,7 @@ import { DialogFooter, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; +import { Skeleton } from '@/components/ui/skeleton'; import { UnifiedMessage, @@ -63,6 +63,7 @@ import { useAgentRunsQuery, useStartAgentMutation, useStopAgentMutation } from ' import { useBillingStatusQuery } from '@/hooks/react-query/threads/use-billing-status'; import { useSubscription, isPlan } from '@/hooks/react-query/subscriptions/use-subscriptions'; import { SubscriptionStatus } from '@/components/thread/chat-input/_use-model-selection'; +import { useThreadAgent } from '@/hooks/react-query/agents/use-agents'; // Extend the base Message type with the expected database fields interface ApiMessageType extends BaseApiMessageType { @@ -136,6 +137,7 @@ export default function ThreadPage({ const messagesLoadedRef = useRef(false); const agentRunsCheckedRef = useRef(false); const previousAgentStatus = useRef('idle'); + const { data: threadAgent, isLoading: threadAgentLoading, error: threadAgentError } = useThreadAgent(threadId); const [externalNavIndex, setExternalNavIndex] = React.useState(undefined); @@ -1223,19 +1225,27 @@ export default function ThreadPage({ "mx-auto", isMobile ? "w-full px-4" : "max-w-3xl" )}> - + {threadAgentLoading || threadAgentError ? ( +
+ + +
+ ) : ( + + )} @@ -1257,6 +1267,7 @@ export default function ThreadPage({ renderAssistantMessage={toolViewAssistant} renderToolResult={toolViewResult} isLoading={!initialLoadCompleted.current || isLoading} + agentName={threadAgentLoading ? 'Loading...' : (threadAgent?.agent?.name || 'Suna')} /> {sandboxId && ( diff --git a/frontend/src/components/dashboard/agent-selector.tsx b/frontend/src/components/dashboard/agent-selector.tsx index c8b329de..2fa1644a 100644 --- a/frontend/src/components/dashboard/agent-selector.tsx +++ b/frontend/src/components/dashboard/agent-selector.tsx @@ -129,7 +129,7 @@ export function AgentSelector({ )} - Your personal AI assistant + Your personal AI employee {agents.length > 0 ? ( @@ -227,7 +227,7 @@ export function AgentSelector({ ) : isUsingSuna ? ( - Your personal AI assistant + Your personal AI employee ) : null} @@ -254,7 +254,7 @@ export function AgentSelector({ )} - Your personal AI assistant + Your personal AI employee {agents.length > 0 ? ( diff --git a/frontend/src/components/thread/chat-input/chat-input.tsx b/frontend/src/components/thread/chat-input/chat-input.tsx index 137fb5b6..7d5d8f95 100644 --- a/frontend/src/components/thread/chat-input/chat-input.tsx +++ b/frontend/src/components/thread/chat-input/chat-input.tsx @@ -39,6 +39,7 @@ export interface ChatInputProps { hideAttachments?: boolean; selectedAgentId?: string; onAgentSelect?: (agentId: string | undefined) => void; + agentName?: string; } export interface UploadedFile { @@ -66,6 +67,7 @@ export const ChatInput = forwardRef( hideAttachments = false, selectedAgentId, onAgentSelect, + agentName, }, ref, ) => { @@ -263,7 +265,7 @@ export const ChatInput = forwardRef( >
- Kortix Suna is working... + {agentName ? `${agentName} is working...` : 'Suna is working...'}
)} diff --git a/frontend/src/components/thread/tool-call-side-panel.tsx b/frontend/src/components/thread/tool-call-side-panel.tsx index 828420fd..1558f24a 100644 --- a/frontend/src/components/thread/tool-call-side-panel.tsx +++ b/frontend/src/components/thread/tool-call-side-panel.tsx @@ -45,6 +45,7 @@ interface ToolCallSidePanelProps { isSuccess?: boolean, ) => React.ReactNode; isLoading?: boolean; + agentName?: string; } interface ToolCallSnapshot { @@ -65,6 +66,7 @@ export function ToolCallSidePanel({ project, isLoading = false, externalNavigateToIndex, + agentName }: ToolCallSidePanelProps) { const [dots, setDots] = React.useState(''); const [internalIndex, setInternalIndex] = React.useState(0); @@ -233,7 +235,7 @@ export function ToolCallSidePanel({

- Computer View + {agentName ? `${agentName}'s Computer` : 'Suna\'s Computer'}