mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-10 14:45:16 -05:00
Fix tool call resolution
This commit is contained in:
@@ -1221,13 +1221,26 @@ function isIntegrationTool(toolName: string): boolean {
|
||||
}
|
||||
|
||||
function shouldShowRunSkipButtons(toolCall: CopilotToolCall): boolean {
|
||||
if (!toolCall.name || toolCall.name === 'unknown_tool') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (toolCall.state !== ClientToolCallState.pending) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Never show buttons for tools the user has marked as always-allowed
|
||||
if (useCopilotStore.getState().autoAllowedTools.includes(toolCall.name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const hasInterrupt = TOOL_DISPLAY_REGISTRY[toolCall.name]?.uiConfig?.interrupt === true
|
||||
if (hasInterrupt && toolCall.state === 'pending') {
|
||||
if (hasInterrupt) {
|
||||
return true
|
||||
}
|
||||
|
||||
const mode = useCopilotStore.getState().mode
|
||||
if (mode === 'build' && isIntegrationTool(toolCall.name) && toolCall.state === 'pending') {
|
||||
if (mode === 'build' && isIntegrationTool(toolCall.name)) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
} from '@/lib/copilot/store-utils'
|
||||
import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-display-registry'
|
||||
import type { CopilotStore, CopilotStreamInfo, CopilotToolCall } from '@/stores/panel/copilot/types'
|
||||
import { useEnvironmentStore } from '@/stores/settings/environment/store'
|
||||
import { useVariablesStore } from '@/stores/panel/variables/store'
|
||||
import { useEnvironmentStore } from '@/stores/settings/environment/store'
|
||||
import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
@@ -499,7 +499,10 @@ export const sseHandlers: Record<string, SSEHandler> = {
|
||||
const { toolCallsById } = get()
|
||||
|
||||
if (!toolCallsById[toolCallId]) {
|
||||
const initialState = ClientToolCallState.pending
|
||||
const isAutoAllowed = get().autoAllowedTools.includes(toolName)
|
||||
const initialState = isAutoAllowed
|
||||
? ClientToolCallState.executing
|
||||
: ClientToolCallState.pending
|
||||
const tc: CopilotToolCall = {
|
||||
id: toolCallId,
|
||||
name: toolName,
|
||||
@@ -524,23 +527,39 @@ export const sseHandlers: Record<string, SSEHandler> = {
|
||||
const { toolCallsById } = get()
|
||||
|
||||
const existing = toolCallsById[id]
|
||||
const toolName = name || existing?.name || 'unknown_tool'
|
||||
const autoAllowedTools = get().autoAllowedTools
|
||||
const isAutoAllowed =
|
||||
autoAllowedTools.includes(toolName) ||
|
||||
(existing?.name ? autoAllowedTools.includes(existing.name) : false)
|
||||
let initialState = isAutoAllowed ? ClientToolCallState.executing : ClientToolCallState.pending
|
||||
|
||||
// Avoid flickering back to pending on partial/duplicate events once a tool is executing.
|
||||
if (
|
||||
existing?.state === ClientToolCallState.executing &&
|
||||
initialState === ClientToolCallState.pending
|
||||
) {
|
||||
initialState = ClientToolCallState.executing
|
||||
}
|
||||
|
||||
const next: CopilotToolCall = existing
|
||||
? {
|
||||
...existing,
|
||||
state: ClientToolCallState.pending,
|
||||
name: toolName,
|
||||
state: initialState,
|
||||
...(args ? { params: args } : {}),
|
||||
display: resolveToolDisplay(name, ClientToolCallState.pending, id, args),
|
||||
display: resolveToolDisplay(toolName, initialState, id, args || existing.params),
|
||||
}
|
||||
: {
|
||||
id,
|
||||
name: name || 'unknown_tool',
|
||||
state: ClientToolCallState.pending,
|
||||
name: toolName,
|
||||
state: initialState,
|
||||
...(args ? { params: args } : {}),
|
||||
display: resolveToolDisplay(name, ClientToolCallState.pending, id, args),
|
||||
display: resolveToolDisplay(toolName, initialState, id, args),
|
||||
}
|
||||
const updated = { ...toolCallsById, [id]: next }
|
||||
set({ toolCallsById: updated })
|
||||
logger.info('[toolCallsById] → pending', { id, name, params: args })
|
||||
logger.info(`[toolCallsById] → ${initialState}`, { id, name: toolName, params: args })
|
||||
|
||||
upsertToolCallBlock(context, next)
|
||||
updateStreamingMessage(set, context)
|
||||
@@ -550,7 +569,7 @@ export const sseHandlers: Record<string, SSEHandler> = {
|
||||
}
|
||||
|
||||
// OAuth: dispatch event to open the OAuth connect modal
|
||||
if (name === 'oauth_request_access' && args && typeof window !== 'undefined') {
|
||||
if (toolName === 'oauth_request_access' && args && typeof window !== 'undefined') {
|
||||
try {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('open-oauth-connect', {
|
||||
|
||||
@@ -190,12 +190,27 @@ export const subAgentSSEHandlers: Record<string, SSEHandler> = {
|
||||
const existingIndex = context.subAgentToolCalls[parentToolCallId].findIndex(
|
||||
(tc: CopilotToolCall) => tc.id === id
|
||||
)
|
||||
const existingToolCall =
|
||||
existingIndex >= 0 ? context.subAgentToolCalls[parentToolCallId][existingIndex] : undefined
|
||||
|
||||
// Auto-allowed tools skip pending state to avoid flashing interrupt buttons
|
||||
const isAutoAllowed = get().autoAllowedTools.includes(name)
|
||||
let initialState = isAutoAllowed ? ClientToolCallState.executing : ClientToolCallState.pending
|
||||
|
||||
// Avoid flickering back to pending on partial/duplicate events once a tool is executing.
|
||||
if (
|
||||
existingToolCall?.state === ClientToolCallState.executing &&
|
||||
initialState === ClientToolCallState.pending
|
||||
) {
|
||||
initialState = ClientToolCallState.executing
|
||||
}
|
||||
|
||||
const subAgentToolCall: CopilotToolCall = {
|
||||
id,
|
||||
name,
|
||||
state: ClientToolCallState.pending,
|
||||
state: initialState,
|
||||
...(args ? { params: args } : {}),
|
||||
display: resolveToolDisplay(name, ClientToolCallState.pending, id, args),
|
||||
display: resolveToolDisplay(name, initialState, id, args),
|
||||
}
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
@@ -231,14 +246,11 @@ export const subAgentSSEHandlers: Record<string, SSEHandler> = {
|
||||
// infer from presence of result data vs error (same logic as server-side
|
||||
// inferToolSuccess). The Go backend uses `*bool` with omitempty so
|
||||
// `success` is present when explicitly set, and absent for non-tool events.
|
||||
const hasExplicitSuccess =
|
||||
data?.success !== undefined || resultData.success !== undefined
|
||||
const hasExplicitSuccess = data?.success !== undefined || resultData.success !== undefined
|
||||
const explicitSuccess = data?.success ?? resultData.success
|
||||
const hasResultData = data?.result !== undefined || resultData.result !== undefined
|
||||
const hasError = !!data?.error || !!resultData.error
|
||||
const success: boolean = hasExplicitSuccess
|
||||
? !!explicitSuccess
|
||||
: hasResultData && !hasError
|
||||
const success: boolean = hasExplicitSuccess ? !!explicitSuccess : hasResultData && !hasError
|
||||
if (!toolCallId) return
|
||||
|
||||
if (!context.subAgentToolCalls[parentToolCallId]) return
|
||||
|
||||
@@ -1,14 +1,42 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { resolveToolDisplay } from '@/lib/copilot/store-utils'
|
||||
import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-display-registry'
|
||||
import type { CopilotMessage, CopilotToolCall } from '@/stores/panel/copilot/types'
|
||||
import { maskCredentialIdsInValue } from './credential-masking'
|
||||
|
||||
const logger = createLogger('CopilotMessageSerialization')
|
||||
|
||||
const TERMINAL_STATES = new Set<string>([
|
||||
ClientToolCallState.success,
|
||||
ClientToolCallState.error,
|
||||
ClientToolCallState.rejected,
|
||||
ClientToolCallState.aborted,
|
||||
ClientToolCallState.review,
|
||||
ClientToolCallState.background,
|
||||
])
|
||||
|
||||
/**
|
||||
* Clears streaming flags and normalizes non-terminal tool call states to 'aborted'.
|
||||
* This ensures that tool calls loaded from DB after a refresh/abort don't render
|
||||
* as in-progress with shimmer animations or interrupt buttons.
|
||||
*/
|
||||
export function clearStreamingFlags(toolCall: CopilotToolCall): void {
|
||||
if (!toolCall) return
|
||||
|
||||
toolCall.subAgentStreaming = false
|
||||
|
||||
// Normalize non-terminal states when loading from DB.
|
||||
// 'executing' → 'success': the server was running it, assume it completed.
|
||||
// 'pending'/'generating' → 'aborted': never reached execution.
|
||||
if (toolCall.state && !TERMINAL_STATES.has(toolCall.state)) {
|
||||
const normalized =
|
||||
toolCall.state === ClientToolCallState.executing
|
||||
? ClientToolCallState.success
|
||||
: ClientToolCallState.aborted
|
||||
toolCall.state = normalized
|
||||
toolCall.display = resolveToolDisplay(toolCall.name, normalized, toolCall.id, toolCall.params)
|
||||
}
|
||||
|
||||
if (Array.isArray(toolCall.subAgentBlocks)) {
|
||||
for (const block of toolCall.subAgentBlocks) {
|
||||
if (block?.type === 'subagent_tool_call' && block.toolCall) {
|
||||
|
||||
Reference in New Issue
Block a user