mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-21 12:51:17 -05:00
Minor refactor
This commit is contained in:
@@ -66,6 +66,28 @@ export class ClaudeDev {
|
|||||||
private providerRef: WeakRef<ClaudeDevProvider>
|
private providerRef: WeakRef<ClaudeDevProvider>
|
||||||
private abort: boolean = false
|
private abort: boolean = false
|
||||||
|
|
||||||
|
// streaming
|
||||||
|
private currentStreamingContentBlockIndex = 0
|
||||||
|
private assistantContentBlocks: AnthropicPartialContentBlock[] = []
|
||||||
|
private toolResults: Anthropic.ToolResultBlockParam[] = []
|
||||||
|
private toolResultsReady = false
|
||||||
|
private didRejectTool = false
|
||||||
|
private presentAssistantContentLocked = false
|
||||||
|
private partialJsonParser: JSONParser | undefined
|
||||||
|
private partialJsonParserState: {
|
||||||
|
partialObject: Record<string, string>
|
||||||
|
currentKey: string
|
||||||
|
currentValue: string
|
||||||
|
parsingKey: boolean
|
||||||
|
parsingValue: boolean
|
||||||
|
} = {
|
||||||
|
partialObject: {},
|
||||||
|
currentKey: "",
|
||||||
|
currentValue: "",
|
||||||
|
parsingKey: false,
|
||||||
|
parsingValue: false,
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
provider: ClaudeDevProvider,
|
provider: ClaudeDevProvider,
|
||||||
apiConfiguration: ApiConfiguration,
|
apiConfiguration: ApiConfiguration,
|
||||||
@@ -1628,33 +1650,11 @@ ${this.customInstructions.trim()}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private currentStreamingContentBlockIndex = 0
|
|
||||||
private assistantContentBlocks: AnthropicPartialContentBlock[] = []
|
|
||||||
private toolResults: Anthropic.ToolResultBlockParam[] = []
|
|
||||||
private toolResultsReady = false
|
|
||||||
private didRejectTool = false
|
|
||||||
|
|
||||||
// lock so it doesnt get spammed ie pwatifor?
|
|
||||||
private isLocked = false
|
|
||||||
async presentAssistantContent() {
|
async presentAssistantContent() {
|
||||||
if (this.isLocked) {
|
if (this.presentAssistantContentLocked) {
|
||||||
console.log("isLocked")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.isLocked = true
|
this.presentAssistantContentLocked = true
|
||||||
|
|
||||||
// when current index finished, then increment and call stream claude content again if contentblocks length has one more.
|
|
||||||
// otherwise check isStreamingComplete, and set toolResultReady for function to continue
|
|
||||||
// if length is more than currentstreamingindex, then ignore it since when currentstreaming is finished it will call this func again
|
|
||||||
|
|
||||||
// if (this.currentStreamingContentBlockIndex !== this.assistantContentBlocks.length - 1) {
|
|
||||||
// console.log(10)
|
|
||||||
// console.log("currentStreamingContentBlockIndex", this.currentStreamingContentBlockIndex)
|
|
||||||
// console.log("assistantContentBlocks.length", this.assistantContentBlocks.length)
|
|
||||||
// // new content past the current streaming index, ignore for now
|
|
||||||
// // this function will be called one last time for a completed block
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
const block = cloneDeep(this.assistantContentBlocks[this.currentStreamingContentBlockIndex]) // need to create copy bc while stream is updating the array, it could be updating the reference block properties too
|
const block = cloneDeep(this.assistantContentBlocks[this.currentStreamingContentBlockIndex]) // need to create copy bc while stream is updating the array, it could be updating the reference block properties too
|
||||||
switch (block.type) {
|
switch (block.type) {
|
||||||
@@ -1684,8 +1684,6 @@ ${this.customInstructions.trim()}
|
|||||||
if (response !== "yesButtonTapped") {
|
if (response !== "yesButtonTapped") {
|
||||||
if (response === "messageResponse") {
|
if (response === "messageResponse") {
|
||||||
await this.say("user_feedback", text, images)
|
await this.say("user_feedback", text, images)
|
||||||
// this.toolResults.push()
|
|
||||||
// const [didUserReject, result] = await this.executeTool(toolName, toolInput)
|
|
||||||
this.toolResults.push({
|
this.toolResults.push({
|
||||||
type: "tool_result",
|
type: "tool_result",
|
||||||
tool_use_id: toolUseId,
|
tool_use_id: toolUseId,
|
||||||
@@ -1697,7 +1695,6 @@ ${this.customInstructions.trim()}
|
|||||||
this.didRejectTool = true
|
this.didRejectTool = true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toolResults.push({
|
this.toolResults.push({
|
||||||
type: "tool_result",
|
type: "tool_result",
|
||||||
tool_use_id: toolUseId,
|
tool_use_id: toolUseId,
|
||||||
@@ -1856,19 +1853,12 @@ ${this.customInstructions.trim()}
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("unlocking")
|
this.presentAssistantContentLocked = false
|
||||||
this.isLocked = false
|
|
||||||
|
|
||||||
console.log(4)
|
|
||||||
if (!block.partial) {
|
if (!block.partial) {
|
||||||
console.log(5)
|
|
||||||
// content is complete, call next block if it exists (if not then read stream will call it when its ready)
|
// content is complete, call next block if it exists (if not then read stream will call it when its ready)
|
||||||
// even if this.didRejectTool, we still need to fill in the tool results with rejection messages
|
// even if this.didRejectTool, we still need to fill in the tool results with rejection messages
|
||||||
this.currentStreamingContentBlockIndex++ // need to increment regardless, so when read stream calls this functio again it will be streaming the next block
|
this.currentStreamingContentBlockIndex++ // need to increment regardless, so when read stream calls this functio again it will be streaming the next block
|
||||||
|
|
||||||
if (this.currentStreamingContentBlockIndex < this.assistantContentBlocks.length) {
|
if (this.currentStreamingContentBlockIndex < this.assistantContentBlocks.length) {
|
||||||
console.log(6)
|
|
||||||
|
|
||||||
// there are already more content blocks to stream, so we'll call this function ourselves
|
// there are already more content blocks to stream, so we'll call this function ourselves
|
||||||
// await this.presentAssistantContent()
|
// await this.presentAssistantContent()
|
||||||
this.presentAssistantContent()
|
this.presentAssistantContent()
|
||||||
@@ -1876,13 +1866,6 @@ ${this.customInstructions.trim()}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private partialJsonParser: JSONParser | undefined
|
|
||||||
// object being built incrementally
|
|
||||||
private partialObject: Record<string, string> = {}
|
|
||||||
private currentKey = ""
|
|
||||||
private currentValue = ""
|
|
||||||
private parsingKey: boolean = false
|
|
||||||
private parsingValue: boolean = false
|
|
||||||
updateAssistantContentWithPartialJson(chunkIndex: number, partialJson: string): Promise<void> {
|
updateAssistantContentWithPartialJson(chunkIndex: number, partialJson: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
@@ -1919,76 +1902,76 @@ ${this.customInstructions.trim()}
|
|||||||
// our json will only ever be string to string maps
|
// our json will only ever be string to string maps
|
||||||
// { "key": "value", "key2": "value2" }
|
// { "key": "value", "key2": "value2" }
|
||||||
// so left brace, string, colon, comma, right brace
|
// so left brace, string, colon, comma, right brace
|
||||||
// Handle each token emitted by the parser
|
// (need to recreate this listener each time to update the resolve ref)
|
||||||
// need to recreate this listener each time to update the resolve ref
|
|
||||||
this.partialJsonParser.onToken = async ({ token, value, offset, partial }) => {
|
this.partialJsonParser.onToken = async ({ token, value, offset, partial }) => {
|
||||||
console.log("onToken")
|
console.log("onToken")
|
||||||
|
const state = this.partialJsonParserState
|
||||||
try {
|
try {
|
||||||
switch (token) {
|
switch (token) {
|
||||||
case TokenType.LEFT_BRACE:
|
case TokenType.LEFT_BRACE:
|
||||||
// Start of a new JSON object
|
// Start of a new JSON object
|
||||||
this.partialObject = {}
|
state.partialObject = {}
|
||||||
this.currentKey = ""
|
state.currentKey = ""
|
||||||
this.parsingKey = false
|
state.parsingKey = false
|
||||||
this.parsingValue = false
|
state.parsingValue = false
|
||||||
break
|
break
|
||||||
case TokenType.RIGHT_BRACE:
|
case TokenType.RIGHT_BRACE:
|
||||||
// End of the current JSON object
|
// End of the current JSON object
|
||||||
this.currentKey = ""
|
state.currentKey = ""
|
||||||
this.currentValue = ""
|
state.currentValue = ""
|
||||||
this.parsingKey = false
|
state.parsingKey = false
|
||||||
this.parsingValue = false
|
state.parsingValue = false
|
||||||
|
|
||||||
// Finalize the object once parsing is complete
|
// Finalize the object once parsing is complete
|
||||||
// ;(this.assistantContentBlocks[chunkIndex] as Anthropic.ToolUseBlock).input = this.partialObject
|
// ;(this.assistantContentBlocks[chunkIndex] as Anthropic.ToolUseBlock).input = this.partialObject
|
||||||
// this.assistantContentBlocks[chunkIndex]!.partial = false
|
// this.assistantContentBlocks[chunkIndex]!.partial = false
|
||||||
// await this.presentAssistantContent() // NOTE: only set partial = false and call this once, since doing it several times will create duplicate messages.
|
// await this.presentAssistantContent() // NOTE: only set partial = false and call this once, since doing it several times will create duplicate messages.
|
||||||
console.log("Final parsed object:", this.partialObject)
|
console.log("Final parsed object:", state.partialObject)
|
||||||
break
|
break
|
||||||
case TokenType.STRING:
|
case TokenType.STRING:
|
||||||
if (!this.parsingValue && !this.parsingKey) {
|
if (!state.parsingValue && !state.parsingKey) {
|
||||||
// Starting to parse a key
|
// Starting to parse a key
|
||||||
this.currentKey = value as string
|
state.currentKey = value as string
|
||||||
this.parsingKey = !!partial // if not partial, we are done parsing key
|
state.parsingKey = !!partial // if not partial, we are done parsing key
|
||||||
} else if (this.parsingKey) {
|
} else if (state.parsingKey) {
|
||||||
// Continuing to parse a key
|
// Continuing to parse a key
|
||||||
this.currentKey = value as string
|
state.currentKey = value as string
|
||||||
this.parsingKey = !!partial
|
state.parsingKey = !!partial
|
||||||
} else if (this.parsingValue) {
|
} else if (state.parsingValue) {
|
||||||
// Parsing a value
|
// Parsing a value
|
||||||
// Accumulate partial value and update the object
|
// Accumulate partial value and update the object
|
||||||
this.currentValue = value as string
|
state.currentValue = value as string
|
||||||
if (this.currentKey) {
|
if (state.currentKey) {
|
||||||
this.partialObject[this.currentKey] = this.currentValue
|
state.partialObject[state.currentKey] = state.currentValue
|
||||||
}
|
}
|
||||||
this.parsingValue = !!partial // if not partial, complete value
|
state.parsingValue = !!partial // if not partial, complete value
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case TokenType.COLON:
|
case TokenType.COLON:
|
||||||
// After a key and colon, expect a value
|
// After a key and colon, expect a value
|
||||||
if (this.currentKey !== null) {
|
if (state.currentKey !== null) {
|
||||||
this.parsingValue = true
|
state.parsingValue = true
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case TokenType.COMMA:
|
case TokenType.COMMA:
|
||||||
// Reset for the next key-value pair
|
// Reset for the next key-value pair
|
||||||
this.currentKey = ""
|
state.currentKey = ""
|
||||||
this.currentValue = ""
|
state.currentValue = ""
|
||||||
this.parsingKey = false
|
state.parsingKey = false
|
||||||
this.parsingValue = false
|
state.parsingValue = false
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
console.error("Unexpected token:", token)
|
console.error("Unexpected token:", token)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugging logs to trace the parsing process
|
// Debugging logs to trace the parsing process
|
||||||
console.log("Partial object:", this.partialObject)
|
console.log("Partial object:", state.partialObject)
|
||||||
console.log("Offset:", offset, "isPartialToken:", partial)
|
console.log("Offset:", offset, "isPartialToken:", partial)
|
||||||
|
|
||||||
// Update the contentBlock with the current state of the partial object
|
// Update the contentBlock with the current state of the partial object
|
||||||
// Use spread operator to ensure a new object reference
|
// Use spread operator to ensure a new object reference
|
||||||
;(this.assistantContentBlocks[chunkIndex] as Anthropic.ToolUseBlock).input = {
|
;(this.assistantContentBlocks[chunkIndex] as Anthropic.ToolUseBlock).input = {
|
||||||
...this.partialObject,
|
...state.partialObject,
|
||||||
}
|
}
|
||||||
// right brace indicates the end of the json object
|
// right brace indicates the end of the json object
|
||||||
this.assistantContentBlocks[chunkIndex]!.partial = token !== TokenType.RIGHT_BRACE
|
this.assistantContentBlocks[chunkIndex]!.partial = token !== TokenType.RIGHT_BRACE
|
||||||
@@ -2048,53 +2031,8 @@ ${this.customInstructions.trim()}
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
// potentially expensive operations
|
const [parsedUserContent, environmentDetails] = await this.loadContext(userContent, includeFileDetails)
|
||||||
const [parsedUserContent, environmentDetails] = await Promise.all([
|
|
||||||
// Process userContent array, which contains various block types:
|
|
||||||
// TextBlockParam, ImageBlockParam, ToolUseBlockParam, and ToolResultBlockParam.
|
|
||||||
// We need to apply parseMentions() to:
|
|
||||||
// 1. All TextBlockParam's text (first user message with task)
|
|
||||||
// 2. ToolResultBlockParam's content/context text arrays if it contains "<feedback>" (see formatToolDeniedFeedback, attemptCompletion, executeCommand, and consecutiveMistakeCount >= 3) or "<answer>" (see askFollowupQuestion), we place all user generated content in these tags so they can effectively be used as markers for when we should parse mentions)
|
|
||||||
Promise.all(
|
|
||||||
userContent.map(async (block) => {
|
|
||||||
if (block.type === "text") {
|
|
||||||
return {
|
|
||||||
...block,
|
|
||||||
text: await parseMentions(block.text, cwd, this.urlContentFetcher),
|
|
||||||
}
|
|
||||||
} else if (block.type === "tool_result") {
|
|
||||||
const isUserMessage = (text: string) => text.includes("<feedback>") || text.includes("<answer>")
|
|
||||||
if (typeof block.content === "string" && isUserMessage(block.content)) {
|
|
||||||
return {
|
|
||||||
...block,
|
|
||||||
content: await parseMentions(block.content, cwd, this.urlContentFetcher),
|
|
||||||
}
|
|
||||||
} else if (Array.isArray(block.content)) {
|
|
||||||
const parsedContent = await Promise.all(
|
|
||||||
block.content.map(async (contentBlock) => {
|
|
||||||
if (contentBlock.type === "text" && isUserMessage(contentBlock.text)) {
|
|
||||||
return {
|
|
||||||
...contentBlock,
|
|
||||||
text: await parseMentions(contentBlock.text, cwd, this.urlContentFetcher),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return contentBlock
|
|
||||||
})
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
...block,
|
|
||||||
content: parsedContent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return block
|
|
||||||
})
|
|
||||||
),
|
|
||||||
this.getEnvironmentDetails(includeFileDetails),
|
|
||||||
])
|
|
||||||
|
|
||||||
userContent = parsedUserContent
|
userContent = parsedUserContent
|
||||||
|
|
||||||
// add environment details as its own text block, separate from tool results
|
// add environment details as its own text block, separate from tool results
|
||||||
userContent.push({ type: "text", text: environmentDetails })
|
userContent.push({ type: "text", text: environmentDetails })
|
||||||
|
|
||||||
@@ -2416,6 +2354,52 @@ ${this.customInstructions.trim()}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadContext(userContent: UserContent, includeFileDetails: boolean = false) {
|
||||||
|
return await Promise.all([
|
||||||
|
// Process userContent array, which contains various block types:
|
||||||
|
// TextBlockParam, ImageBlockParam, ToolUseBlockParam, and ToolResultBlockParam.
|
||||||
|
// We need to apply parseMentions() to:
|
||||||
|
// 1. All TextBlockParam's text (first user message with task)
|
||||||
|
// 2. ToolResultBlockParam's content/context text arrays if it contains "<feedback>" (see formatToolDeniedFeedback, attemptCompletion, executeCommand, and consecutiveMistakeCount >= 3) or "<answer>" (see askFollowupQuestion), we place all user generated content in these tags so they can effectively be used as markers for when we should parse mentions)
|
||||||
|
Promise.all(
|
||||||
|
userContent.map(async (block) => {
|
||||||
|
if (block.type === "text") {
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
text: await parseMentions(block.text, cwd, this.urlContentFetcher),
|
||||||
|
}
|
||||||
|
} else if (block.type === "tool_result") {
|
||||||
|
const isUserMessage = (text: string) => text.includes("<feedback>") || text.includes("<answer>")
|
||||||
|
if (typeof block.content === "string" && isUserMessage(block.content)) {
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
content: await parseMentions(block.content, cwd, this.urlContentFetcher),
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(block.content)) {
|
||||||
|
const parsedContent = await Promise.all(
|
||||||
|
block.content.map(async (contentBlock) => {
|
||||||
|
if (contentBlock.type === "text" && isUserMessage(contentBlock.text)) {
|
||||||
|
return {
|
||||||
|
...contentBlock,
|
||||||
|
text: await parseMentions(contentBlock.text, cwd, this.urlContentFetcher),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contentBlock
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
content: parsedContent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return block
|
||||||
|
})
|
||||||
|
),
|
||||||
|
this.getEnvironmentDetails(includeFileDetails),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
// Formatting responses to Claude
|
// Formatting responses to Claude
|
||||||
|
|
||||||
private formatImagesIntoBlocks(images?: string[]): Anthropic.ImageBlockParam[] {
|
private formatImagesIntoBlocks(images?: string[]): Anthropic.ImageBlockParam[] {
|
||||||
|
|||||||
Reference in New Issue
Block a user