mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-17 09:52:38 -05:00
Superagent
This commit is contained in:
42
apps/sim/app/agent/page.tsx
Normal file
42
apps/sim/app/agent/page.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import { Copilot } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/copilot'
|
||||
import { useCopilotStore } from '@/stores/panel/copilot/store'
|
||||
|
||||
/**
|
||||
* Superagent page - standalone AI agent with full credential access
|
||||
* Uses the exact same Copilot UI but with superagent mode forced
|
||||
*/
|
||||
export default function AgentPage() {
|
||||
const { setMode } = useCopilotStore()
|
||||
|
||||
// Set superagent mode on mount
|
||||
useEffect(() => {
|
||||
setMode('superagent')
|
||||
}, [setMode])
|
||||
|
||||
return (
|
||||
<Tooltip.Provider delayDuration={600} skipDelayDuration={0}>
|
||||
<div className='flex h-screen flex-col bg-[var(--surface-1)]'>
|
||||
{/* Header */}
|
||||
<header className='flex h-14 flex-shrink-0 items-center justify-between border-b border-[var(--border)] px-4'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<h1 className='font-semibold text-lg text-[var(--text-primary)]'>Superagent</h1>
|
||||
<span className='rounded-full bg-[var(--accent)]/10 px-2 py-0.5 font-medium text-[var(--accent)] text-xs'>
|
||||
Full Access
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Copilot - exact same component in standalone mode */}
|
||||
<div className='flex-1 overflow-hidden p-4'>
|
||||
<div className='mx-auto h-full max-w-4xl'>
|
||||
<Copilot panelWidth={800} standalone />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip.Provider>
|
||||
)
|
||||
}
|
||||
@@ -38,7 +38,7 @@ const ChatMessageSchema = z.object({
|
||||
message: z.string().min(1, 'Message is required'),
|
||||
userMessageId: z.string().optional(), // ID from frontend for the user message
|
||||
chatId: z.string().optional(),
|
||||
workflowId: z.string().min(1, 'Workflow ID is required'),
|
||||
workflowId: z.string().optional(),
|
||||
model: z
|
||||
.enum([
|
||||
'gpt-5-fast',
|
||||
@@ -63,7 +63,7 @@ const ChatMessageSchema = z.object({
|
||||
])
|
||||
.optional()
|
||||
.default('claude-4.5-opus'),
|
||||
mode: z.enum(['ask', 'agent', 'plan']).optional().default('agent'),
|
||||
mode: z.enum(['ask', 'agent', 'plan', 'superagent']).optional().default('agent'),
|
||||
prefetch: z.boolean().optional(),
|
||||
createNewChat: z.boolean().optional().default(false),
|
||||
stream: z.boolean().optional().default(true),
|
||||
@@ -339,7 +339,7 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
} | null = null
|
||||
|
||||
if (mode === 'agent') {
|
||||
if (mode === 'agent' || mode === 'superagent') {
|
||||
// Build base tools (executed locally, not deferred)
|
||||
// Include function_execute for code execution capability
|
||||
baseTools = [
|
||||
|
||||
@@ -25,7 +25,7 @@ const ExecuteToolSchema = z.object({
|
||||
toolCallId: z.string(),
|
||||
toolName: z.string(),
|
||||
arguments: z.record(z.any()).optional().default({}),
|
||||
workflowId: z.string().optional(),
|
||||
workflowId: z.string().nullish(), // Accept undefined or null for superagent mode
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -276,7 +276,7 @@ function shouldShowRunSkipButtons(toolCall: CopilotToolCall): boolean {
|
||||
const mode = useCopilotStore.getState().mode
|
||||
const isAutoAllowed = useCopilotStore.getState().isToolAutoAllowed(toolCall.name)
|
||||
if (
|
||||
mode === 'build' &&
|
||||
(mode === 'build' || mode === 'superagent') &&
|
||||
isIntegrationTool(toolCall.name) &&
|
||||
toolCall.state === 'pending' &&
|
||||
!isAutoAllowed
|
||||
@@ -564,11 +564,11 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
|
||||
|
||||
// Allow rendering if:
|
||||
// 1. Tool is in CLASS_TOOL_METADATA (client tools), OR
|
||||
// 2. We're in build mode (integration tools are executed server-side)
|
||||
// 2. We're in build or superagent mode (integration tools are executed server-side)
|
||||
const isClientTool = !!CLASS_TOOL_METADATA[toolCall.name]
|
||||
const isIntegrationToolInBuildMode = mode === 'build' && !isClientTool
|
||||
const isIntegrationToolInAgentMode = (mode === 'build' || mode === 'superagent') && !isClientTool
|
||||
|
||||
if (!isClientTool && !isIntegrationToolInBuildMode) {
|
||||
if (!isClientTool && !isIntegrationToolInAgentMode) {
|
||||
return null
|
||||
}
|
||||
const isExpandableTool =
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { ListTree, MessageSquare, Package } from 'lucide-react'
|
||||
import { ListTree, MessageSquare, Package, Zap } from 'lucide-react'
|
||||
import {
|
||||
Badge,
|
||||
Popover,
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
|
||||
interface ModeSelectorProps {
|
||||
/** Current mode - 'ask', 'build', or 'plan' */
|
||||
mode: 'ask' | 'build' | 'plan'
|
||||
/** Current mode - 'ask', 'build', 'plan', or 'superagent' */
|
||||
mode: 'ask' | 'build' | 'plan' | 'superagent'
|
||||
/** Callback when mode changes */
|
||||
onModeChange?: (mode: 'ask' | 'build' | 'plan') => void
|
||||
onModeChange?: (mode: 'ask' | 'build' | 'plan' | 'superagent') => void
|
||||
/** Whether the input is near the top of viewport (affects dropdown direction) */
|
||||
isNearTop: boolean
|
||||
/** Whether the selector is disabled */
|
||||
@@ -42,6 +42,9 @@ export function ModeSelector({ mode, onModeChange, isNearTop, disabled }: ModeSe
|
||||
if (mode === 'plan') {
|
||||
return <ListTree className='h-3 w-3' />
|
||||
}
|
||||
if (mode === 'superagent') {
|
||||
return <Zap className='h-3 w-3' />
|
||||
}
|
||||
return <Package className='h-3 w-3' />
|
||||
}
|
||||
|
||||
@@ -52,10 +55,13 @@ export function ModeSelector({ mode, onModeChange, isNearTop, disabled }: ModeSe
|
||||
if (mode === 'plan') {
|
||||
return 'Plan'
|
||||
}
|
||||
if (mode === 'superagent') {
|
||||
return 'Superagent'
|
||||
}
|
||||
return 'Build'
|
||||
}
|
||||
|
||||
const handleSelect = (selectedMode: 'ask' | 'build' | 'plan') => {
|
||||
const handleSelect = (selectedMode: 'ask' | 'build' | 'plan' | 'superagent') => {
|
||||
onModeChange?.(selectedMode)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ interface UserInputProps {
|
||||
isAborting?: boolean
|
||||
placeholder?: string
|
||||
className?: string
|
||||
mode?: 'ask' | 'build' | 'plan'
|
||||
onModeChange?: (mode: 'ask' | 'build' | 'plan') => void
|
||||
mode?: 'ask' | 'build' | 'plan' | 'superagent'
|
||||
onModeChange?: (mode: 'ask' | 'build' | 'plan' | 'superagent') => void
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
panelWidth?: number
|
||||
|
||||
@@ -8,8 +8,8 @@ import { Button } from '@/components/emcn'
|
||||
interface WelcomeProps {
|
||||
/** Callback when a suggested question is clicked */
|
||||
onQuestionClick?: (question: string) => void
|
||||
/** Current copilot mode ('ask' for Q&A, 'plan' for planning, 'build' for workflow building) */
|
||||
mode?: 'ask' | 'build' | 'plan'
|
||||
/** Current copilot mode ('ask' for Q&A, 'plan' for planning, 'build' for workflow building, 'superagent' for full access) */
|
||||
mode?: 'ask' | 'build' | 'plan' | 'superagent'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,8 @@ const logger = createLogger('Copilot')
|
||||
interface CopilotProps {
|
||||
/** Width of the copilot panel in pixels */
|
||||
panelWidth: number
|
||||
/** If true, runs in standalone mode without workflow context (for superagent) */
|
||||
standalone?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +69,7 @@ interface CopilotRef {
|
||||
* Copilot component - AI-powered assistant for workflow management
|
||||
* Provides chat interface, message history, and intelligent workflow suggestions
|
||||
*/
|
||||
export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref) => {
|
||||
export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth, standalone = false }, ref) => {
|
||||
const userInputRef = useRef<UserInputRef>(null)
|
||||
const copilotContainerRef = useRef<HTMLDivElement>(null)
|
||||
const cancelEditCallbackRef = useRef<(() => void) | null>(null)
|
||||
@@ -122,6 +124,7 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref
|
||||
loadAutoAllowedTools,
|
||||
currentChat,
|
||||
isSendingMessage,
|
||||
standalone,
|
||||
})
|
||||
|
||||
// Handle scroll management
|
||||
@@ -298,7 +301,7 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref
|
||||
*/
|
||||
const handleSubmit = useCallback(
|
||||
async (query: string, fileAttachments?: MessageFileAttachment[], contexts?: any[]) => {
|
||||
if (!query || isSendingMessage || !activeWorkflowId) return
|
||||
if (!query || isSendingMessage || (!activeWorkflowId && !standalone)) return
|
||||
|
||||
if (showPlanTodos) {
|
||||
const store = useCopilotStore.getState()
|
||||
@@ -316,7 +319,7 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref
|
||||
logger.error('Failed to send message:', error)
|
||||
}
|
||||
},
|
||||
[isSendingMessage, activeWorkflowId, sendMessage, showPlanTodos]
|
||||
[isSendingMessage, activeWorkflowId, sendMessage, showPlanTodos, standalone]
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -487,11 +490,11 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref
|
||||
ref={userInputRef}
|
||||
onSubmit={handleSubmit}
|
||||
onAbort={handleAbort}
|
||||
disabled={!activeWorkflowId}
|
||||
disabled={!activeWorkflowId && !standalone}
|
||||
isLoading={isSendingMessage}
|
||||
isAborting={isAborting}
|
||||
mode={mode}
|
||||
onModeChange={setMode}
|
||||
onModeChange={standalone ? undefined : setMode}
|
||||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
panelWidth={panelWidth}
|
||||
@@ -594,11 +597,11 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref
|
||||
ref={userInputRef}
|
||||
onSubmit={handleSubmit}
|
||||
onAbort={handleAbort}
|
||||
disabled={!activeWorkflowId}
|
||||
disabled={!activeWorkflowId && !standalone}
|
||||
isLoading={isSendingMessage}
|
||||
isAborting={isAborting}
|
||||
mode={mode}
|
||||
onModeChange={setMode}
|
||||
onModeChange={standalone ? undefined : setMode}
|
||||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
panelWidth={panelWidth}
|
||||
|
||||
@@ -15,6 +15,8 @@ interface UseCopilotInitializationProps {
|
||||
loadAutoAllowedTools: () => Promise<void>
|
||||
currentChat: any
|
||||
isSendingMessage: boolean
|
||||
/** If true, initializes without requiring a workflowId (for standalone agent mode) */
|
||||
standalone?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,6 +36,7 @@ export function useCopilotInitialization(props: UseCopilotInitializationProps) {
|
||||
loadAutoAllowedTools,
|
||||
currentChat,
|
||||
isSendingMessage,
|
||||
standalone = false,
|
||||
} = props
|
||||
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
@@ -46,6 +49,14 @@ export function useCopilotInitialization(props: UseCopilotInitializationProps) {
|
||||
* Never loads during message streaming to prevent interrupting active conversations
|
||||
*/
|
||||
useEffect(() => {
|
||||
// Standalone mode: initialize immediately without workflow
|
||||
if (standalone && !hasMountedRef.current && !isSendingMessage) {
|
||||
hasMountedRef.current = true
|
||||
setIsInitialized(true)
|
||||
logger.info('Standalone mode initialized')
|
||||
return
|
||||
}
|
||||
|
||||
if (activeWorkflowId && !hasMountedRef.current && !isSendingMessage) {
|
||||
hasMountedRef.current = true
|
||||
setIsInitialized(false)
|
||||
@@ -55,7 +66,7 @@ export function useCopilotInitialization(props: UseCopilotInitializationProps) {
|
||||
// Use false to let the store decide if a reload is needed based on cache
|
||||
loadChats(false)
|
||||
}
|
||||
}, [activeWorkflowId, setCopilotWorkflowId, loadChats, isSendingMessage])
|
||||
}, [activeWorkflowId, setCopilotWorkflowId, loadChats, isSendingMessage, standalone])
|
||||
|
||||
/**
|
||||
* Initialize the component - only on mount and genuine workflow changes
|
||||
@@ -63,6 +74,9 @@ export function useCopilotInitialization(props: UseCopilotInitializationProps) {
|
||||
* Never reloads during message streaming to preserve active conversations
|
||||
*/
|
||||
useEffect(() => {
|
||||
// Skip workflow tracking in standalone mode
|
||||
if (standalone) return
|
||||
|
||||
// Handle genuine workflow changes (not initial mount, not same workflow)
|
||||
// Only reload if not currently streaming to avoid interrupting conversations
|
||||
if (
|
||||
@@ -100,19 +114,23 @@ export function useCopilotInitialization(props: UseCopilotInitializationProps) {
|
||||
setCopilotWorkflowId,
|
||||
loadChats,
|
||||
isSendingMessage,
|
||||
standalone,
|
||||
])
|
||||
|
||||
/**
|
||||
* Fetch context usage when component is initialized and has a current chat
|
||||
*/
|
||||
useEffect(() => {
|
||||
// In standalone mode, skip context usage fetch (no workflow context)
|
||||
if (standalone) return
|
||||
|
||||
if (isInitialized && currentChat?.id && activeWorkflowId) {
|
||||
logger.info('[Copilot] Component initialized, fetching context usage')
|
||||
fetchContextUsage().catch((err) => {
|
||||
logger.warn('[Copilot] Failed to fetch context usage on mount', err)
|
||||
})
|
||||
}
|
||||
}, [isInitialized, currentChat?.id, activeWorkflowId, fetchContextUsage])
|
||||
}, [isInitialized, currentChat?.id, activeWorkflowId, fetchContextUsage, standalone])
|
||||
|
||||
/**
|
||||
* Load auto-allowed tools once on mount
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface CopilotMessage {
|
||||
* Chat config stored in database
|
||||
*/
|
||||
export interface CopilotChatConfig {
|
||||
mode?: 'ask' | 'build' | 'plan'
|
||||
mode?: 'ask' | 'build' | 'plan' | 'superagent'
|
||||
model?: string
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export interface SendMessageRequest {
|
||||
userMessageId?: string // ID from frontend for the user message
|
||||
chatId?: string
|
||||
workflowId?: string
|
||||
mode?: 'ask' | 'agent' | 'plan'
|
||||
mode?: 'ask' | 'agent' | 'plan' | 'superagent'
|
||||
model?:
|
||||
| 'gpt-5-fast'
|
||||
| 'gpt-5'
|
||||
|
||||
@@ -1073,16 +1073,16 @@ const sseHandlers: Record<string, SSEHandler> = {
|
||||
|
||||
// Integration tools: Check if auto-allowed, otherwise wait for user confirmation
|
||||
// This handles tools like google_calendar_*, exa_*, etc. that aren't in the client registry
|
||||
// Only relevant if mode is 'build' (agent)
|
||||
// Relevant in 'build' mode (with workflow) or 'superagent' mode (standalone)
|
||||
const { mode, workflowId, autoAllowedTools } = get()
|
||||
if (mode === 'build' && workflowId) {
|
||||
if ((mode === 'build' && workflowId) || mode === 'superagent') {
|
||||
// Check if tool was NOT found in client registry (def is undefined from above)
|
||||
const def = name ? getTool(name) : undefined
|
||||
const inst = getClientTool(id) as any
|
||||
if (!def && !inst && name) {
|
||||
// Check if this tool is auto-allowed
|
||||
if (autoAllowedTools.includes(name)) {
|
||||
logger.info('[build mode] Integration tool auto-allowed, executing', { id, name })
|
||||
logger.info('[copilot] Integration tool auto-allowed, executing', { id, name, mode })
|
||||
|
||||
// Auto-execute the tool
|
||||
setTimeout(() => {
|
||||
@@ -1090,9 +1090,10 @@ const sseHandlers: Record<string, SSEHandler> = {
|
||||
}, 0)
|
||||
} else {
|
||||
// Integration tools stay in pending state until user confirms
|
||||
logger.info('[build mode] Integration tool awaiting user confirmation', {
|
||||
logger.info('[copilot] Integration tool awaiting user confirmation', {
|
||||
id,
|
||||
name,
|
||||
mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1982,7 +1983,8 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
messageId?: string
|
||||
}
|
||||
|
||||
if (!workflowId) return
|
||||
// Allow sending without workflowId in superagent mode
|
||||
if (!workflowId && mode !== 'superagent') return
|
||||
|
||||
const abortController = new AbortController()
|
||||
set({ isSendingMessage: true, error: null, abortController })
|
||||
@@ -2053,8 +2055,8 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
}
|
||||
|
||||
// Call copilot API
|
||||
const apiMode: 'ask' | 'agent' | 'plan' =
|
||||
mode === 'ask' ? 'ask' : mode === 'plan' ? 'plan' : 'agent'
|
||||
const apiMode: 'ask' | 'agent' | 'plan' | 'superagent' =
|
||||
mode === 'ask' ? 'ask' : mode === 'plan' ? 'plan' : mode === 'superagent' ? 'superagent' : 'agent'
|
||||
const result = await sendStreamingMessage({
|
||||
message: messageToSend,
|
||||
userMessageId: userMessage.id,
|
||||
@@ -2916,9 +2918,10 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
},
|
||||
|
||||
executeIntegrationTool: async (toolCallId: string) => {
|
||||
const { toolCallsById, workflowId } = get()
|
||||
const { toolCallsById, workflowId, mode } = get()
|
||||
const toolCall = toolCallsById[toolCallId]
|
||||
if (!toolCall || !workflowId) return
|
||||
// In superagent mode, workflowId is optional
|
||||
if (!toolCall || (!workflowId && mode !== 'superagent')) return
|
||||
|
||||
const { id, name, params } = toolCall
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ import type { CopilotChat as ApiCopilotChat } from '@/lib/copilot/api'
|
||||
|
||||
export type CopilotChat = ApiCopilotChat
|
||||
|
||||
export type CopilotMode = 'ask' | 'build' | 'plan'
|
||||
export type CopilotMode = 'ask' | 'build' | 'plan' | 'superagent'
|
||||
|
||||
export interface CopilotState {
|
||||
mode: CopilotMode
|
||||
|
||||
Reference in New Issue
Block a user