This commit is contained in:
Siddharth Ganesan
2026-01-09 18:26:42 -08:00
committed by Emir Karabeg
parent 0ba5ec65f7
commit 6639871c92
15 changed files with 87 additions and 68 deletions

View File

@@ -62,7 +62,7 @@ export async function POST(req: NextRequest) {
}
const body = await req.json()
// Debug: Log what we received
const lastMsg = body.messages?.[body.messages.length - 1]
if (lastMsg?.role === 'assistant') {
@@ -74,7 +74,7 @@ export async function POST(req: NextRequest) {
lastMsgContentBlockTypes: lastMsg.contentBlocks?.map((b: any) => b?.type) || [],
})
}
const { chatId, messages, planArtifact, config } = UpdateMessagesSchema.parse(body)
// Verify that the chat belongs to the user

View File

@@ -246,7 +246,7 @@ export function ThinkingBlock({
)}
>
{/* Render markdown during streaming with thinking text styling */}
<div className='whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-muted)] leading-none [&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-none [&_*]:!m-0 [&_*]:!p-0 [&_*]:!mb-0 [&_*]:!mt-0 [&_p]:!m-0 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_code]:!text-[11px] [&_ul]:!pl-4 [&_ul]:!my-0 [&_ol]:!pl-4 [&_ol]:!my-0 [&_li]:!my-0 [&_li]:!py-0 [&_br]:!leading-[0.5]'>
<div className='[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-none [&_*]:!m-0 [&_*]:!p-0 [&_*]:!mb-0 [&_*]:!mt-0 [&_p]:!m-0 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_code]:!text-[11px] [&_ul]:!pl-4 [&_ul]:!my-0 [&_ol]:!pl-4 [&_ol]:!my-0 [&_li]:!my-0 [&_li]:!py-0 [&_br]:!leading-[0.5] whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-muted)] leading-none'>
<CopilotMarkdownRenderer content={content} />
<span className='ml-1 inline-block h-2 w-1 animate-pulse bg-[var(--text-muted)]' />
</div>
@@ -286,7 +286,7 @@ export function ThinkingBlock({
)}
>
{/* Use markdown renderer for completed content */}
<div className='whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-muted)] leading-none [&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-none [&_*]:!m-0 [&_*]:!p-0 [&_*]:!mb-0 [&_*]:!mt-0 [&_p]:!m-0 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_code]:!text-[11px] [&_ul]:!pl-4 [&_ul]:!my-0 [&_ol]:!pl-4 [&_ol]:!my-0 [&_li]:!my-0 [&_li]:!py-0 [&_br]:!leading-[0.5]'>
<div className='[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-none [&_*]:!m-0 [&_*]:!p-0 [&_*]:!mb-0 [&_*]:!mt-0 [&_p]:!m-0 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_code]:!text-[11px] [&_ul]:!pl-4 [&_ul]:!my-0 [&_ol]:!pl-4 [&_ol]:!my-0 [&_li]:!my-0 [&_li]:!py-0 [&_br]:!leading-[0.5] whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-muted)] leading-none'>
<CopilotMarkdownRenderer content={content} />
</div>
</div>

View File

@@ -514,7 +514,9 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
options={parsedTags.options}
onSelect={handleOptionSelect}
disabled={isSendingMessage || isStreaming}
enableKeyboardNav={isLastMessage && !isStreaming && parsedTags.optionsComplete === true}
enableKeyboardNav={
isLastMessage && !isStreaming && parsedTags.optionsComplete === true
}
streaming={isStreaming || !parsedTags.optionsComplete}
/>
)}

View File

@@ -10,10 +10,10 @@ import { getRegisteredTools } from '@/lib/copilot/tools/client/registry'
// Initialize all tool UI configs
import '@/lib/copilot/tools/client/init-tool-configs'
import {
getToolUIConfig,
isSpecialTool as isSpecialToolFromConfig,
getSubagentLabels as getSubagentLabelsFromConfig,
getToolUIConfig,
hasInterrupt as hasInterruptFromConfig,
isSpecialTool as isSpecialToolFromConfig,
} from '@/lib/copilot/tools/client/ui-config'
import CopilotMarkdownRenderer from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/markdown-renderer'
import { SmoothStreamingText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/smooth-streaming'
@@ -357,19 +357,21 @@ export function OptionsSelector({
disabled && 'cursor-not-allowed opacity-50',
streaming && 'pointer-events-none',
isLocked && 'cursor-default',
isHovered && !streaming && 'is-hovered bg-[var(--surface-6)] dark:bg-[var(--surface-5)]'
isHovered &&
!streaming &&
'is-hovered bg-[var(--surface-6)] dark:bg-[var(--surface-5)]'
)}
>
<Button
variant='3d'
className='group-hover:-translate-y-0.5 group-[.is-hovered]:-translate-y-0.5 w-[22px] py-[2px] text-[11px] group-hover:text-[var(--text-primary)] group-[.is-hovered]:text-[var(--text-primary)] group-hover:shadow-[0_4px_0_0_rgba(48,48,48,1)] group-[.is-hovered]:shadow-[0_4px_0_0_rgba(48,48,48,1)]'
className='group-hover:-translate-y-0.5 group-[.is-hovered]:-translate-y-0.5 w-[22px] py-[2px] text-[11px] group-hover:text-[var(--text-primary)] group-hover:shadow-[0_4px_0_0_rgba(48,48,48,1)] group-[.is-hovered]:text-[var(--text-primary)] group-[.is-hovered]:shadow-[0_4px_0_0_rgba(48,48,48,1)]'
>
{option.key}
</Button>
<span
className={clsx(
'min-w-0 flex-1 pt-0.5 font-season text-[12px] leading-5 text-[var(--text-tertiary)] group-hover:text-[var(--text-primary)] group-[.is-hovered]:text-[var(--text-primary)] [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-[11px] [&_p]:m-0 [&_p]:leading-5',
'min-w-0 flex-1 pt-0.5 font-season text-[12px] text-[var(--text-tertiary)] leading-5 group-hover:text-[var(--text-primary)] group-[.is-hovered]:text-[var(--text-primary)] [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-[11px] [&_p]:m-0 [&_p]:leading-5',
isRejected && 'text-[var(--text-tertiary)] line-through opacity-50'
)}
>
@@ -1158,7 +1160,9 @@ function SubagentContentRenderer({
}, [isStreaming, shouldCollapse])
// Build segments: each segment is either text content or a tool call
const segments: Array<{ type: 'text'; content: string } | { type: 'tool'; block: SubAgentContentBlock }> = []
const segments: Array<
{ type: 'text'; content: string } | { type: 'tool'; block: SubAgentContentBlock }
> = []
let currentText = ''
let allRawText = ''
@@ -1916,20 +1920,21 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
'workflow',
]
const isSubagentTool = SUBAGENT_TOOLS.includes(toolCall.name)
// For ALL subagent tools, don't show anything until we have blocks with content
if (isSubagentTool) {
// Check if we have any meaningful content in blocks
const hasContent = toolCall.subAgentBlocks && toolCall.subAgentBlocks.some(block =>
(block.type === 'subagent_text' && block.content?.trim()) ||
(block.type === 'subagent_tool_call' && block.toolCall)
const hasContent = toolCall.subAgentBlocks?.some(
(block) =>
(block.type === 'subagent_text' && block.content?.trim()) ||
(block.type === 'subagent_tool_call' && block.toolCall)
)
if (!hasContent) {
return null
}
}
if (isSubagentTool && toolCall.subAgentBlocks && toolCall.subAgentBlocks.length > 0) {
// Render subagent content using the dedicated component
return (
@@ -1975,9 +1980,9 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
// Check UI config for secondary action
const toolUIConfig = getToolUIConfig(toolCall.name)
const secondaryAction = toolUIConfig?.secondaryAction
const showSecondaryAction =
secondaryAction &&
secondaryAction.showInStates.includes(toolCall.state as ClientToolCallState)
const showSecondaryAction = secondaryAction?.showInStates.includes(
toolCall.state as ClientToolCallState
)
// Legacy fallbacks for tools that haven't migrated to UI config
const showMoveToBackground =
@@ -2396,7 +2401,10 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
)}
{/* Render subagent content as thinking text */}
{toolCall.subAgentBlocks && toolCall.subAgentBlocks.length > 0 && (
<SubAgentThinkingContent blocks={toolCall.subAgentBlocks} isStreaming={toolCall.subAgentStreaming} />
<SubAgentThinkingContent
blocks={toolCall.subAgentBlocks}
isStreaming={toolCall.subAgentStreaming}
/>
)}
</div>
)
@@ -2455,7 +2463,10 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
)}
{/* Render subagent content as thinking text */}
{toolCall.subAgentBlocks && toolCall.subAgentBlocks.length > 0 && (
<SubAgentThinkingContent blocks={toolCall.subAgentBlocks} isStreaming={toolCall.subAgentStreaming} />
<SubAgentThinkingContent
blocks={toolCall.subAgentBlocks}
isStreaming={toolCall.subAgentStreaming}
/>
)}
</div>
)
@@ -2564,7 +2575,10 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
{/* Render subagent content as thinking text */}
{toolCall.subAgentBlocks && toolCall.subAgentBlocks.length > 0 && (
<SubAgentThinkingContent blocks={toolCall.subAgentBlocks} isStreaming={toolCall.subAgentStreaming} />
<SubAgentThinkingContent
blocks={toolCall.subAgentBlocks}
isStreaming={toolCall.subAgentStreaming}
/>
)}
</div>
)

View File

@@ -121,7 +121,10 @@ export const ToolArgSchemas = {
serverId: z
.string()
.describe('The MCP server ID to deploy to (get from list_workspace_mcp_servers)'),
workflowId: z.string().optional().describe('Optional workflow ID (defaults to active workflow)'),
workflowId: z
.string()
.optional()
.describe('Optional workflow ID (defaults to active workflow)'),
toolName: z.string().optional().describe('Custom tool name (defaults to workflow name)'),
toolDescription: z.string().optional().describe('Custom tool description'),
parameterDescriptions: z

View File

@@ -8,11 +8,7 @@
* Examples: edit, plan, debug, evaluate, research, etc.
*/
import type { LucideIcon } from 'lucide-react'
import {
BaseClientTool,
type BaseClientToolMetadata,
ClientToolCallState,
} from './base-tool'
import { BaseClientTool, type BaseClientToolMetadata, ClientToolCallState } from './base-tool'
import type { SubagentConfig, ToolUIConfig } from './ui-config'
import { registerToolUIConfig } from './ui-config'
@@ -122,4 +118,3 @@ export function createSubagentToolClass(config: SubagentToolConfig) {
}
}
}

View File

@@ -35,15 +35,14 @@ import './user/set-environment-variables'
// Re-export UI config utilities for convenience
export {
getToolUIConfig,
isSubagentTool,
isSpecialTool,
hasInterrupt,
getSubagentLabels,
type ToolUIConfig,
type SubagentConfig,
getToolUIConfig,
hasInterrupt,
type InterruptConfig,
type SecondaryActionConfig,
isSpecialTool,
isSubagentTool,
type ParamsTableConfig,
type SecondaryActionConfig,
type SubagentConfig,
type ToolUIConfig,
} from './ui-config'

View File

@@ -236,4 +236,3 @@ export function getSubagentLabels(
export function getAllToolUIConfigs(): Record<string, ToolUIConfig> {
return { ...toolUIConfigs }
}

View File

@@ -153,4 +153,3 @@ export class CreateWorkspaceMcpServerClientTool extends BaseClientTool {
await this.handleAccept(args)
}
}

View File

@@ -288,4 +288,3 @@ export class DeployApiClientTool extends BaseClientTool {
// Register UI config at module load
registerToolUIConfig(DeployApiClientTool.id, DeployApiClientTool.metadata.uiConfig!)

View File

@@ -363,4 +363,3 @@ export class DeployChatClientTool extends BaseClientTool {
// Register UI config at module load
registerToolUIConfig(DeployChatClientTool.id, DeployChatClientTool.metadata.uiConfig!)

View File

@@ -6,7 +6,6 @@ import {
ClientToolCallState,
} from '@/lib/copilot/tools/client/base-tool'
import { registerToolUIConfig } from '@/lib/copilot/tools/client/ui-config'
import { useCopilotStore } from '@/stores/panel/copilot/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
export interface ParameterDescription {
@@ -118,7 +117,9 @@ export class DeployMcpClientTool extends BaseClientTool {
}
// Check if workflow is deployed
const deploymentStatus = useWorkflowRegistry.getState().getWorkflowDeploymentStatus(workflowId)
const deploymentStatus = useWorkflowRegistry
.getState()
.getWorkflowDeploymentStatus(workflowId)
if (!deploymentStatus?.isDeployed) {
throw new Error(
'Workflow must be deployed before adding as an MCP tool. Use deploy_api first.'
@@ -208,4 +209,3 @@ export class DeployMcpClientTool extends BaseClientTool {
// Register UI config at module load
registerToolUIConfig(DeployMcpClientTool.id, DeployMcpClientTool.metadata.uiConfig!)

View File

@@ -92,10 +92,14 @@ export class ListWorkspaceMcpServersClientTool extends BaseClientTool {
{ servers: [], count: 0 }
)
} else {
await this.markToolComplete(200, `Found ${servers.length} MCP server(s) in the workspace.`, {
servers,
count: servers.length,
})
await this.markToolComplete(
200,
`Found ${servers.length} MCP server(s) in the workspace.`,
{
servers,
count: servers.length,
}
)
}
logger.info(`Listed ${servers.length} MCP servers`)
@@ -106,4 +110,3 @@ export class ListWorkspaceMcpServersClientTool extends BaseClientTool {
}
}
}

View File

@@ -57,7 +57,6 @@ import { DeployApiClientTool } from '@/lib/copilot/tools/client/workflow/deploy-
import { DeployChatClientTool } from '@/lib/copilot/tools/client/workflow/deploy-chat'
import { DeployMcpClientTool } from '@/lib/copilot/tools/client/workflow/deploy-mcp'
import { EditWorkflowClientTool } from '@/lib/copilot/tools/client/workflow/edit-workflow'
import { ListWorkspaceMcpServersClientTool } from '@/lib/copilot/tools/client/workflow/list-workspace-mcp-servers'
import { GetBlockOutputsClientTool } from '@/lib/copilot/tools/client/workflow/get-block-outputs'
import { GetBlockUpstreamReferencesClientTool } from '@/lib/copilot/tools/client/workflow/get-block-upstream-references'
import { GetUserWorkflowClientTool } from '@/lib/copilot/tools/client/workflow/get-user-workflow'
@@ -65,6 +64,7 @@ import { GetWorkflowConsoleClientTool } from '@/lib/copilot/tools/client/workflo
import { GetWorkflowDataClientTool } from '@/lib/copilot/tools/client/workflow/get-workflow-data'
import { GetWorkflowFromNameClientTool } from '@/lib/copilot/tools/client/workflow/get-workflow-from-name'
import { ListUserWorkflowsClientTool } from '@/lib/copilot/tools/client/workflow/list-user-workflows'
import { ListWorkspaceMcpServersClientTool } from '@/lib/copilot/tools/client/workflow/list-workspace-mcp-servers'
import { ManageCustomToolClientTool } from '@/lib/copilot/tools/client/workflow/manage-custom-tool'
import { ManageMcpToolClientTool } from '@/lib/copilot/tools/client/workflow/manage-mcp-tool'
import { RunWorkflowClientTool } from '@/lib/copilot/tools/client/workflow/run-workflow'
@@ -399,9 +399,9 @@ function normalizeMessagesForUI(messages: CopilotMessage[]): CopilotMessage[] {
if (message.role === 'assistant') {
logger.info('[normalizeMessagesForUI] Loading assistant message', {
id: message.id,
hasContent: !!(message.content && message.content.trim()),
hasContent: !!message.content?.trim(),
contentBlockCount: message.contentBlocks?.length || 0,
contentBlockTypes: (message.contentBlocks as any[])?.map(b => b?.type) || [],
contentBlockTypes: (message.contentBlocks as any[])?.map((b) => b?.type) || [],
})
}
}
@@ -642,9 +642,9 @@ function serializeMessagesForDB(messages: CopilotMessage[]): any[] {
if (msg.role === 'assistant') {
logger.info('[serializeMessagesForDB] Input assistant message', {
id: msg.id,
hasContent: !!(msg.content && msg.content.trim()),
hasContent: !!msg.content?.trim(),
contentBlockCount: msg.contentBlocks?.length || 0,
contentBlockTypes: (msg.contentBlocks as any[])?.map(b => b?.type) || [],
contentBlockTypes: (msg.contentBlocks as any[])?.map((b) => b?.type) || [],
})
}
}
@@ -652,12 +652,15 @@ function serializeMessagesForDB(messages: CopilotMessage[]): any[] {
logger.info('[serializeMessagesForDB] Serialized messages', {
inputCount: messages.length,
outputCount: result.length,
sample: result.length > 0 ? {
role: result[result.length - 1].role,
hasContent: !!result[result.length - 1].content,
contentBlockCount: result[result.length - 1].contentBlocks?.length || 0,
toolCallCount: result[result.length - 1].toolCalls?.length || 0,
} : null
sample:
result.length > 0
? {
role: result[result.length - 1].role,
hasContent: !!result[result.length - 1].content,
contentBlockCount: result[result.length - 1].contentBlocks?.length || 0,
toolCallCount: result[result.length - 1].toolCalls?.length || 0,
}
: null,
})
return result
@@ -3139,7 +3142,7 @@ export const useCopilotStore = create<CopilotStore>()(
contentLength: lastMsg.content?.length || 0,
hasContentBlocks: !!lastMsg.contentBlocks,
contentBlockCount: lastMsg.contentBlocks?.length || 0,
contentBlockTypes: (lastMsg.contentBlocks as any[])?.map(b => b?.type) || [],
contentBlockTypes: (lastMsg.contentBlocks as any[])?.map((b) => b?.type) || [],
})
}
const dbMessages = validateMessagesForLLM(currentMessages)

View File

@@ -448,9 +448,11 @@ export const useWorkflowDiffStore = create<WorkflowDiffState & WorkflowDiffActio
findLatestEditWorkflowToolCallId().then((toolCallId) => {
if (toolCallId) {
getClientTool(toolCallId)?.handleAccept?.()?.catch?.((error: Error) => {
logger.warn('Failed to notify tool accept state', { error })
})
getClientTool(toolCallId)
?.handleAccept?.()
?.catch?.((error: Error) => {
logger.warn('Failed to notify tool accept state', { error })
})
}
})
},
@@ -554,9 +556,11 @@ export const useWorkflowDiffStore = create<WorkflowDiffState & WorkflowDiffActio
findLatestEditWorkflowToolCallId().then((toolCallId) => {
if (toolCallId) {
getClientTool(toolCallId)?.handleReject?.()?.catch?.((error: Error) => {
logger.warn('Failed to notify tool reject state', { error })
})
getClientTool(toolCallId)
?.handleReject?.()
?.catch?.((error: Error) => {
logger.warn('Failed to notify tool reject state', { error })
})
}
})
},