Skip to content

Commit 5f45db4

Browse files
authored
improvement(copilot): variables, conditions, router (#2887)
* Temp * Condition and router copilot syntax updates * Plan respond plan
1 parent 81cbfe7 commit 5f45db4

File tree

4 files changed

+226
-103
lines changed

4 files changed

+226
-103
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ import { CLASS_TOOL_METADATA } from '@/stores/panel/copilot/store'
2626
import type { SubAgentContentBlock } from '@/stores/panel/copilot/types'
2727
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
2828

29-
/**
30-
* Parse special tags from content
31-
*/
3229
/**
3330
* Plan step can be either a string or an object with title and plan
3431
*/
@@ -47,6 +44,56 @@ interface ParsedTags {
4744
cleanContent: string
4845
}
4946

47+
/**
48+
* Extract plan steps from plan_respond tool calls in subagent blocks.
49+
* Returns { steps, isComplete } where steps is in the format expected by PlanSteps component.
50+
*/
51+
function extractPlanFromBlocks(blocks: SubAgentContentBlock[] | undefined): {
52+
steps: Record<string, PlanStep> | undefined
53+
isComplete: boolean
54+
} {
55+
if (!blocks) return { steps: undefined, isComplete: false }
56+
57+
// Find the plan_respond tool call
58+
const planRespondBlock = blocks.find(
59+
(b) => b.type === 'subagent_tool_call' && b.toolCall?.name === 'plan_respond'
60+
)
61+
62+
if (!planRespondBlock?.toolCall) {
63+
return { steps: undefined, isComplete: false }
64+
}
65+
66+
// Tool call arguments can be in different places depending on the source
67+
// Also handle nested data.arguments structure from the schema
68+
const tc = planRespondBlock.toolCall as any
69+
const args = tc.params || tc.parameters || tc.input || tc.arguments || tc.data?.arguments || {}
70+
const stepsArray = args.steps
71+
72+
if (!Array.isArray(stepsArray) || stepsArray.length === 0) {
73+
return { steps: undefined, isComplete: false }
74+
}
75+
76+
// Convert array format to Record<string, PlanStep> format
77+
// From: [{ number: 1, title: "..." }, { number: 2, title: "..." }]
78+
// To: { "1": "...", "2": "..." }
79+
const steps: Record<string, PlanStep> = {}
80+
for (const step of stepsArray) {
81+
if (step.number !== undefined && step.title) {
82+
steps[String(step.number)] = step.title
83+
}
84+
}
85+
86+
// Check if the tool call is complete (not pending/executing)
87+
const isComplete =
88+
planRespondBlock.toolCall.state === ClientToolCallState.success ||
89+
planRespondBlock.toolCall.state === ClientToolCallState.error
90+
91+
return {
92+
steps: Object.keys(steps).length > 0 ? steps : undefined,
93+
isComplete,
94+
}
95+
}
96+
5097
/**
5198
* Try to parse partial JSON for streaming options.
5299
* Attempts to extract complete key-value pairs from incomplete JSON.
@@ -654,11 +701,20 @@ function SubAgentThinkingContent({
654701
}
655702
}
656703

704+
// Extract plan from plan_respond tool call (preferred) or fall back to <plan> tags
705+
const { steps: planSteps, isComplete: planComplete } = extractPlanFromBlocks(blocks)
657706
const allParsed = parseSpecialTags(allRawText)
658707

659-
if (!cleanText.trim() && !allParsed.plan) return null
708+
// Prefer plan_respond tool data over <plan> tags
709+
const hasPlan =
710+
!!(planSteps && Object.keys(planSteps).length > 0) ||
711+
!!(allParsed.plan && Object.keys(allParsed.plan).length > 0)
712+
const planToRender = planSteps || allParsed.plan
713+
const isPlanStreaming = planSteps ? !planComplete : isStreaming
660714

661-
const hasSpecialTags = !!(allParsed.plan && Object.keys(allParsed.plan).length > 0)
715+
if (!cleanText.trim() && !hasPlan) return null
716+
717+
const hasSpecialTags = hasPlan
662718

663719
return (
664720
<div className='space-y-1.5'>
@@ -670,9 +726,7 @@ function SubAgentThinkingContent({
670726
hasSpecialTags={hasSpecialTags}
671727
/>
672728
)}
673-
{allParsed.plan && Object.keys(allParsed.plan).length > 0 && (
674-
<PlanSteps steps={allParsed.plan} streaming={isStreaming} />
675-
)}
729+
{hasPlan && planToRender && <PlanSteps steps={planToRender} streaming={isPlanStreaming} />}
676730
</div>
677731
)
678732
}
@@ -744,8 +798,19 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
744798
}
745799

746800
const allParsed = parseSpecialTags(allRawText)
801+
802+
// Extract plan from plan_respond tool call (preferred) or fall back to <plan> tags
803+
const { steps: planSteps, isComplete: planComplete } = extractPlanFromBlocks(
804+
toolCall.subAgentBlocks
805+
)
806+
const hasPlan =
807+
!!(planSteps && Object.keys(planSteps).length > 0) ||
808+
!!(allParsed.plan && Object.keys(allParsed.plan).length > 0)
809+
const planToRender = planSteps || allParsed.plan
810+
const isPlanStreaming = planSteps ? !planComplete : isStreaming
811+
747812
const hasSpecialTags = !!(
748-
(allParsed.plan && Object.keys(allParsed.plan).length > 0) ||
813+
hasPlan ||
749814
(allParsed.options && Object.keys(allParsed.options).length > 0)
750815
)
751816

@@ -757,8 +822,6 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
757822
const outerLabel = getSubagentCompletionLabel(toolCall.name)
758823
const durationText = `${outerLabel} for ${formatDuration(duration)}`
759824

760-
const hasPlan = allParsed.plan && Object.keys(allParsed.plan).length > 0
761-
762825
const renderCollapsibleContent = () => (
763826
<>
764827
{segments.map((segment, index) => {
@@ -800,7 +863,7 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
800863
return (
801864
<div className='w-full space-y-1.5'>
802865
{renderCollapsibleContent()}
803-
{hasPlan && <PlanSteps steps={allParsed.plan!} streaming={isStreaming} />}
866+
{hasPlan && planToRender && <PlanSteps steps={planToRender} streaming={isPlanStreaming} />}
804867
</div>
805868
)
806869
}
@@ -832,7 +895,7 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
832895
</div>
833896

834897
{/* Plan stays outside the collapsible */}
835-
{hasPlan && <PlanSteps steps={allParsed.plan!} />}
898+
{hasPlan && planToRender && <PlanSteps steps={planToRender} />}
836899
</div>
837900
)
838901
})
@@ -1412,7 +1475,11 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
14121475
if (
14131476
toolCall.name === 'checkoff_todo' ||
14141477
toolCall.name === 'mark_todo_in_progress' ||
1415-
toolCall.name === 'tool_search_tool_regex'
1478+
toolCall.name === 'tool_search_tool_regex' ||
1479+
toolCall.name === 'user_memory' ||
1480+
toolCall.name === 'edit_responsd' ||
1481+
toolCall.name === 'debug_respond' ||
1482+
toolCall.name === 'plan_respond'
14161483
)
14171484
return null
14181485

apps/sim/lib/copilot/tools/client/workflow/set-global-workflow-variables.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,17 @@ export class SetGlobalWorkflowVariablesClientTool extends BaseClientTool {
209209
}
210210
}
211211

212-
const variablesArray = Object.values(byName)
212+
// Convert byName (keyed by name) to record keyed by ID for the API
213+
const variablesRecord: Record<string, any> = {}
214+
for (const v of Object.values(byName)) {
215+
variablesRecord[v.id] = v
216+
}
213217

214-
// POST full variables array to persist
218+
// POST full variables record to persist
215219
const res = await fetch(`/api/workflows/${payload.workflowId}/variables`, {
216220
method: 'POST',
217221
headers: { 'Content-Type': 'application/json' },
218-
body: JSON.stringify({ variables: variablesArray }),
222+
body: JSON.stringify({ variables: variablesRecord }),
219223
})
220224
if (!res.ok) {
221225
const txt = await res.text().catch(() => '')

0 commit comments

Comments
 (0)