From d7699834833037dfc8498cf8a5e4b39f44199dc5 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 11 Jul 2025 15:34:02 -0700 Subject: [PATCH] feat: Add component for write technical notes tool call (#395) * feat: Add component for write technical notes tool call * cr --- .../src/graphs/planner/nodes/notetaker.ts | 15 +-- .../gen-ui/write-technical-notes.tsx | 94 +++++++++++++++++++ .../web/src/components/thread/messages/ai.tsx | 31 ++++++ packages/shared/src/open-swe/tools.ts | 17 ++++ 4 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 apps/web/src/components/gen-ui/write-technical-notes.tsx diff --git a/apps/open-swe/src/graphs/planner/nodes/notetaker.ts b/apps/open-swe/src/graphs/planner/nodes/notetaker.ts index 6c8665b3..e8c35e59 100644 --- a/apps/open-swe/src/graphs/planner/nodes/notetaker.ts +++ b/apps/open-swe/src/graphs/planner/nodes/notetaker.ts @@ -12,6 +12,7 @@ import { formatCustomRulesPrompt } from "../../../utils/custom-rules.js"; import { getPlannerNotes } from "../utils/get-notes.js"; import { ToolMessage } from "@langchain/core/messages"; import { DO_NOT_RENDER_ID_PREFIX } from "@open-swe/shared/constants"; +import { createWriteTechnicalNotesToolFields } from "@open-swe/shared/open-swe/tools"; const PLANNER_NOTES_PROMPT = `You've also taken technical notes throughout the context gathering process. Ensure you include/incorporate these notes, or the highest quality parts of these notes in your conclusion notes. @@ -93,17 +94,7 @@ const formatPrompt = (state: PlannerGraphState): string => { ); }; -const condenseContextToolSchema = z.object({ - notes: z - .string() - .describe("The notes you've generated based on the conversation history."), -}); -const condenseContextTool = { - name: "write_technical_notes", - description: - "Write technical notes based on the conversation history provided. Ensure these notes are concise, but still containing enough information to be useful to you when you go to execute the plan.", - schema: condenseContextToolSchema, -}; +const condenseContextTool = createWriteTechnicalNotesToolFields(); export async function notetaker( state: PlannerGraphState, @@ -144,7 +135,7 @@ ${state.messages.map(getMessageString).join("\n")}`; return { messages: [response, toolResponse], contextGatheringNotes: ( - toolCall.args as z.infer + toolCall.args as z.infer ).notes, }; } diff --git a/apps/web/src/components/gen-ui/write-technical-notes.tsx b/apps/web/src/components/gen-ui/write-technical-notes.tsx new file mode 100644 index 00000000..92f17131 --- /dev/null +++ b/apps/web/src/components/gen-ui/write-technical-notes.tsx @@ -0,0 +1,94 @@ +"use client"; + +import { useState } from "react"; +import { + CheckCircle, + Loader2, + ChevronDown, + ChevronUp, + FileText, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +type WriteTechnicalNotesProps = { + status: "generating" | "done"; + notes?: string; + reasoningText?: string; +}; + +export function WriteTechnicalNotes({ + status, + notes, + reasoningText, +}: WriteTechnicalNotesProps) { + const [expanded, setExpanded] = useState(false); + + const getStatusIcon = () => { + switch (status) { + case "generating": + return ( + + ); + case "done": + return ; + } + }; + + const getStatusText = () => { + switch (status) { + case "generating": + return "Writing technical notes..."; + case "done": + return "Technical notes written"; + } + }; + + const shouldShowToggle = () => { + return status === "done" && notes; + }; + + return ( +
+
+ + + Technical Notes + +
+ + {getStatusText()} + + {getStatusIcon()} + {shouldShowToggle() && ( + + )} +
+
+ + {expanded && notes && status === "done" && ( +
+
+            {notes}
+          
+
+ )} + + {reasoningText && status === "done" && ( +
+

+ {reasoningText} +

+
+ )} +
+ ); +} diff --git a/apps/web/src/components/thread/messages/ai.tsx b/apps/web/src/components/thread/messages/ai.tsx index 675ed570..a2704dfd 100644 --- a/apps/web/src/components/thread/messages/ai.tsx +++ b/apps/web/src/components/thread/messages/ai.tsx @@ -22,6 +22,7 @@ import { ActionStep, ActionItemProps } from "@/components/gen-ui/action-step"; import { TaskSummary } from "@/components/gen-ui/task-summary"; import { PullRequestOpened } from "@/components/gen-ui/pull-request-opened"; import { DiagnoseErrorAction } from "@/components/v2/diagnose-error-action"; +import { WriteTechnicalNotes } from "@/components/gen-ui/write-technical-notes"; import { ToolCall } from "@langchain/core/messages/tool"; import { createApplyPatchToolFields, @@ -34,6 +35,7 @@ import { createDiagnoseErrorToolFields, createGetURLContentToolFields, createFindInstancesOfToolFields, + createWriteTechnicalNotesToolFields, } from "@open-swe/shared/open-swe/tools"; import { z } from "zod"; import { isAIMessageSDK, isToolMessageSDK } from "@/lib/langchain-messages"; @@ -67,6 +69,11 @@ type GetURLContentToolArgs = z.infer; const findInstancesOfTool = createFindInstancesOfToolFields(dummyRepo); type FindInstancesOfToolArgs = z.infer; +const writeTechnicalNotesTool = createWriteTechnicalNotesToolFields(); +type WriteTechnicalNotesToolArgs = z.infer< + typeof writeTechnicalNotesTool.schema +>; + function CustomComponent({ message, thread, @@ -305,6 +312,10 @@ export function AssistantMessage({ ? aiToolCalls.find((tc) => tc.name === diagnoseErrorTool.name) : undefined; + const writeTechnicalNotesToolCall = message + ? aiToolCalls.find((tc) => tc.name === writeTechnicalNotesTool.name) + : undefined; + // We can be sure that if the task status tool call is present, it will be the // only tool call/result we need to render for this message. if (taskStatusToolCall) { @@ -346,6 +357,26 @@ export function AssistantMessage({ ); } + if (writeTechnicalNotesToolCall) { + const correspondingToolResult = toolResults.find( + (tr) => tr && tr.tool_call_id === writeTechnicalNotesToolCall.id, + ); + + const args = + writeTechnicalNotesToolCall.args as WriteTechnicalNotesToolArgs; + const reasoningText = getContentString(content); + + return ( +
+ +
+ ); + } + if (openPrToolCall) { let branch: string | undefined; let targetBranch: string | undefined = "main"; diff --git a/packages/shared/src/open-swe/tools.ts b/packages/shared/src/open-swe/tools.ts index bb2c658b..13ba368d 100644 --- a/packages/shared/src/open-swe/tools.ts +++ b/packages/shared/src/open-swe/tools.ts @@ -342,3 +342,20 @@ export function createGetURLContentToolFields() { schema: getURLContentSchema, }; } + +export function createWriteTechnicalNotesToolFields() { + const writeTechnicalNotesSchema = z.object({ + notes: z + .string() + .describe( + "The notes you've generated based on the conversation history.", + ), + }); + + return { + name: "write_technical_notes", + description: + "Write technical notes based on the conversation history provided. Ensure these notes are concise, but still containing enough information to be useful to you when you go to execute the plan.", + schema: writeTechnicalNotesSchema, + }; +}