diff --git a/src/core/ClaudeDev.ts b/src/core/ClaudeDev.ts
index 4768208..05f8a2e 100644
--- a/src/core/ClaudeDev.ts
+++ b/src/core/ClaudeDev.ts
@@ -227,21 +227,24 @@ export class ClaudeDev {
}
let askTs: number
if (partial !== undefined) {
- const lastMessage = this.claudeMessages.at(-1)
+ const lastMessageOfType = findLast(this.claudeMessages, (m) => m.ask === type)
const isUpdatingPreviousPartial =
- lastMessage && lastMessage.partial && lastMessage.type === "ask" && lastMessage.ask === type
+ lastMessageOfType &&
+ lastMessageOfType.partial &&
+ lastMessageOfType.type === "ask" &&
+ lastMessageOfType.ask === type
if (partial) {
if (isUpdatingPreviousPartial) {
// existing partial message, so update it
- lastMessage.text = text
- lastMessage.partial = partial
+ lastMessageOfType.text = text
+ lastMessageOfType.partial = partial
// todo be more efficient about saving and posting only new data or one whole message at a time so ignore partial for saves, and only post parts of partial message instead of whole array in new listener
// await this.saveClaudeMessages()
// await this.providerRef.deref()?.postStateToWebview()
await this.providerRef
.deref()
- ?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
- throw new Error("Current ask promise was ignored")
+ ?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessageOfType })
+ throw new Error("Current ask promise was ignored 1")
} else {
// this is a new partial message, so add it with partial state
// this.askResponse = undefined
@@ -251,7 +254,7 @@ export class ClaudeDev {
// this.lastMessageTs = askTs
await this.addToClaudeMessages({ ts: Date.now(), type: "ask", ask: type, text, partial })
await this.providerRef.deref()?.postStateToWebview()
- throw new Error("Current ask promise was ignored")
+ throw new Error("Current ask promise was ignored 2")
}
} else {
// partial=false means its a complete version of a previously partial message
@@ -262,9 +265,9 @@ export class ClaudeDev {
this.askResponseImages = undefined
askTs = Date.now()
this.lastMessageTs = askTs
- lastMessage.ts = askTs
- lastMessage.text = text
- lastMessage.partial = false
+ lastMessageOfType.ts = askTs
+ lastMessageOfType.text = text
+ lastMessageOfType.partial = false
await this.saveClaudeMessages()
await this.providerRef.deref()?.postStateToWebview()
} else {
@@ -319,20 +322,23 @@ export class ClaudeDev {
}
if (partial !== undefined) {
- const lastMessage = this.claudeMessages.at(-1)
+ const lastMessageOfType = findLast(this.claudeMessages, (m) => m.say === type)
const isUpdatingPreviousPartial =
- lastMessage && lastMessage.partial && lastMessage.type === "say" && lastMessage.say === type
+ lastMessageOfType &&
+ lastMessageOfType.partial &&
+ lastMessageOfType.type === "say" &&
+ lastMessageOfType.say === type
if (partial) {
if (isUpdatingPreviousPartial) {
// existing partial message, so update it
- lastMessage.text = text
- lastMessage.images = images
- lastMessage.partial = partial
+ lastMessageOfType.text = text
+ lastMessageOfType.images = images
+ lastMessageOfType.partial = partial
// await this.saveClaudeMessages()
// await this.providerRef.deref()?.postStateToWebview()
await this.providerRef
.deref()
- ?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
+ ?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessageOfType })
} else {
// this is a new partial message, so add it with partial state
@@ -345,10 +351,10 @@ export class ClaudeDev {
// this is the complete version of a previously partial message, so replace the partial with the complete version
const sayTs = Date.now()
this.lastMessageTs = sayTs
- lastMessage.ts = sayTs
- lastMessage.text = text
- lastMessage.images = images
- lastMessage.partial = false
+ lastMessageOfType.ts = sayTs
+ lastMessageOfType.text = text
+ lastMessageOfType.images = images
+ lastMessageOfType.partial = false
// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
await this.saveClaudeMessages()
@@ -1673,16 +1679,22 @@ ${this.customInstructions.trim()}
}
// If the last API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
- const lastApiReqFinished = findLast(this.claudeMessages, (m) => m.say === "api_req_finished")
- if (lastApiReqFinished && lastApiReqFinished.text) {
+ const lastApiReqStarted = findLast(this.claudeMessages, (m) => m.say === "api_req_started")
+ if (lastApiReqStarted && lastApiReqStarted.text) {
const {
tokensIn,
tokensOut,
cacheWrites,
cacheReads,
}: { tokensIn?: number; tokensOut?: number; cacheWrites?: number; cacheReads?: number } = JSON.parse(
- lastApiReqFinished.text
+ lastApiReqStarted.text
)
+ console.log("lastApiReqStarted", lastApiReqStarted.text, {
+ tokensIn,
+ tokensOut,
+ cacheWrites,
+ cacheReads,
+ })
const totalTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
const contextWindow = this.api.getModel().info.contextWindow
const maxAllowedSize = Math.max(contextWindow - 40_000, contextWindow * 0.8)
@@ -2494,9 +2506,6 @@ ${this.customInstructions.trim()}
textContent.content = textContentLines.join("\n")
this.assistantMessageContent = [textContent, ...toolCalls]
-
- // Present the updated content
- this.presentAssistantMessage()
}
async recursivelyMakeClaudeRequests(
@@ -2692,23 +2701,31 @@ ${this.customInstructions.trim()}
let totalCost: string | undefined
- await this.say(
- "api_req_finished",
- JSON.stringify({
- tokensIn: inputTokens,
- tokensOut: outputTokens,
- cacheWrites: cacheCreationInputTokens,
- cacheReads: cacheReadInputTokens,
- cost:
- totalCost ||
- this.calculateApiCost(
- inputTokens,
- outputTokens,
- cacheCreationInputTokens,
- cacheReadInputTokens
- ),
- })
- )
+ // update api_req_started. we can't use api_req_finished anymore since it's a unique case where it could come after a streaming message (ie in the middle of being updated or executed)
+ // fortunately api_req_finished was always parsed out for the gui anyways, so it remains solely for legacy purposes to keep track of prices in tasks from history
+ // (it's worth removing a few months from now)
+ this.claudeMessages[lastApiReqIndex].text = JSON.stringify({
+ ...JSON.parse(this.claudeMessages[lastApiReqIndex].text),
+ tokensIn: inputTokens,
+ tokensOut: outputTokens,
+ cacheWrites: cacheCreationInputTokens,
+ cacheReads: cacheReadInputTokens,
+ cost:
+ totalCost ||
+ this.calculateApiCost(inputTokens, outputTokens, cacheCreationInputTokens, cacheReadInputTokens),
+ })
+ await this.saveClaudeMessages()
+ await this.providerRef.deref()?.postStateToWebview()
+
+ // await this.say(
+ // "api_req_finished",
+ // JSON.stringify({
+
+ // })
+ // )
+
+ // console.log("apiContentBlocks", apiContentBlocks)
+ // throw new Error("ClaudeDev fail")
// now add to apiconversationhistory
// need to save assistant responses to file before proceeding to tool use since user can exit at any moment and we wouldn't be able to save the assistant's response
@@ -2725,6 +2742,10 @@ ${this.customInstructions.trim()}
await pWaitFor(() => this.userMessageContentReady)
+ console.log("attempted to send new request")
+
+ // throw new Error("ClaudeDev fail")
+
const recDidEndLoop = await this.recursivelyMakeClaudeRequests(this.userMessageContent)
didEndLoop = recDidEndLoop
} else {
diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts
index a361aab..d16f6f6 100644
--- a/src/core/prompts/system.ts
+++ b/src/core/prompts/system.ts
@@ -77,15 +77,14 @@ INSTRUCTIONS FOR FORMULATING YOUR RESPONSE
You must respond to the user's request by using at least one tool call. When formulating your response, follow these guidelines:
-1. Begin your response with normal text, explaining your thoughts, analysis, or plan of action.
-2. If you need to use any tools, place ALL tool calls at the END of your message, after your normal text explanation.
+1. You might begin your response explaining your thoughts, analysis, plan of action, etc.
+2. Place ALL tool calls at the END of your message.
3. You can use multiple tool calls if needed, but they should all be grouped together at the end of your message.
-4. After placing the tool calls, do not add any additional normal text. The tool calls should be the final content in your message.
Here's the general structure your responses should follow:
\`\`\`
-[Your normal text response explaining your thoughts and actions]
+...Your thoughts...
[Tool Call 1]
[Tool Call 2 if needed]
@@ -96,10 +95,25 @@ Here's the general structure your responses should follow:
Remember:
- Choose the most appropriate tool(s) based on the task and the tool descriptions provided.
- Formulate your tool calls using the XML format specified for each tool.
-- Provide clear explanations in your normal text about what actions you're taking and why you're using particular tools.
+- Provide clear explanations about what actions you're taking and why you're using particular tools.
- Act as if the tool calls will be executed immediately after your message, and your next response will have access to their results.
-# Tool Descriptions and XML Formats
+# Tool Calls Formatting
+
+Tool calls are formatted with the name of the tool enclosed in XML tags on their own line.
+Each parameter is defined within its own set of XML tags, also each on their own line.
+Example:
+
+
+value1
+
+
+value2
+
+
+Ensure that each tool call follows this structure for consistent parsing and execution.
+
+# Tool Descriptions
## execute_command
@@ -217,14 +231,10 @@ Parameters:
- command: (optional) A CLI command to execute to show a live demo of the result to the user. For example, use 'open index.html' to display a created website. This command should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.
-# Examples
-
-Here are some examples of how to structure your responses with tool calls:
+# Tool Calls Examples
## Example 1: Using a single tool
-Let's run the test suite for our project. This will help us ensure that all our components are functioning correctly.
-
npm test
@@ -233,8 +243,6 @@ npm test
## Example 2: Using multiple tools
-Let's create two new configuration files for the web application, one for the frontend and one for the backend.
-
./frontend-config.json
@@ -281,8 +289,6 @@ externalServices:
## Example 3: Asking a follow-up question
-I've analyzed the project structure, but I need more information to proceed. Let me ask the user for clarification.
-
Which specific feature would you like me to implement in the example.py file?
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx
index acde688..dab9e02 100644
--- a/webview-ui/src/components/chat/ChatView.tsx
+++ b/webview-ui/src/components/chat/ChatView.tsx
@@ -426,23 +426,29 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
[isAtBottom, visibleMessages, expandedRows]
)
+ const [lastScrollMessageCount, setLastScrollMessageCount] = useState(0)
+
useEffect(() => {
- // dont scroll if we're just updating the api req started informational body
const lastMessage = visibleMessages.at(-1)
- const isLastApiReqStarted = lastMessage?.say === "api_req_started"
- if (didScrollFromApiReqTs && isLastApiReqStarted && lastMessage?.ts === didScrollFromApiReqTs) {
- return
+ if (lastMessage?.partial && isAtBottom) {
+ virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: "auto" })
+ } else {
+ // dont scroll if we're just updating the api req started informational body
+
+ const isLastApiReqStarted = lastMessage?.say === "api_req_started"
+ if (didScrollFromApiReqTs && isLastApiReqStarted && lastMessage?.ts === didScrollFromApiReqTs) {
+ return
+ }
+ // We use a setTimeout to ensure new content is rendered before scrolling to the bottom. virtuoso's followOutput would scroll to the bottom before the new content could render.
+ const timer = setTimeout(() => {
+ // TODO: we can use virtuoso's isAtBottom to prevent scrolling if user is scrolled up, and show a 'scroll to bottom' button for better UX
+ // NOTE: scroll to bottom may not work if you use margin, see virtuoso's troubleshooting
+ virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: "smooth" })
+ setDidScrollFromApiReqTs(isLastApiReqStarted ? lastMessage?.ts : undefined) // need to do this in timer since this effect can get called a few times simultaneously
+ }, 50)
+
+ return () => clearTimeout(timer)
}
-
- // We use a setTimeout to ensure new content is rendered before scrolling to the bottom. virtuoso's followOutput would scroll to the bottom before the new content could render.
- const timer = setTimeout(() => {
- // TODO: we can use virtuoso's isAtBottom to prevent scrolling if user is scrolled up, and show a 'scroll to bottom' button for better UX
- // NOTE: scroll to bottom may not work if you use margin, see virtuoso's troubleshooting
- virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: "smooth" })
- setDidScrollFromApiReqTs(isLastApiReqStarted ? lastMessage?.ts : undefined) // need to do this in timer since this effect can get called a few times simultaneously
- }, 50)
-
- return () => clearTimeout(timer)
}, [visibleMessages, didScrollFromApiReqTs])
const placeholderText = useMemo(() => {