mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-21 04:41:16 -05:00
Minor refactor
This commit is contained in:
@@ -66,6 +66,28 @@ export class ClaudeDev {
|
||||
private providerRef: WeakRef<ClaudeDevProvider>
|
||||
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(
|
||||
provider: ClaudeDevProvider,
|
||||
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() {
|
||||
if (this.isLocked) {
|
||||
console.log("isLocked")
|
||||
if (this.presentAssistantContentLocked) {
|
||||
return
|
||||
}
|
||||
this.isLocked = 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
|
||||
// }
|
||||
this.presentAssistantContentLocked = true
|
||||
|
||||
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) {
|
||||
@@ -1684,8 +1684,6 @@ ${this.customInstructions.trim()}
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
// this.toolResults.push()
|
||||
// const [didUserReject, result] = await this.executeTool(toolName, toolInput)
|
||||
this.toolResults.push({
|
||||
type: "tool_result",
|
||||
tool_use_id: toolUseId,
|
||||
@@ -1697,7 +1695,6 @@ ${this.customInstructions.trim()}
|
||||
this.didRejectTool = true
|
||||
return false
|
||||
}
|
||||
|
||||
this.toolResults.push({
|
||||
type: "tool_result",
|
||||
tool_use_id: toolUseId,
|
||||
@@ -1856,19 +1853,12 @@ ${this.customInstructions.trim()}
|
||||
break
|
||||
}
|
||||
|
||||
console.log("unlocking")
|
||||
this.isLocked = false
|
||||
|
||||
console.log(4)
|
||||
this.presentAssistantContentLocked = false
|
||||
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)
|
||||
// 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
|
||||
|
||||
if (this.currentStreamingContentBlockIndex < this.assistantContentBlocks.length) {
|
||||
console.log(6)
|
||||
|
||||
// there are already more content blocks to stream, so we'll call this function ourselves
|
||||
// await 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> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
@@ -1919,76 +1902,76 @@ ${this.customInstructions.trim()}
|
||||
// our json will only ever be string to string maps
|
||||
// { "key": "value", "key2": "value2" }
|
||||
// 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 }) => {
|
||||
console.log("onToken")
|
||||
const state = this.partialJsonParserState
|
||||
try {
|
||||
switch (token) {
|
||||
case TokenType.LEFT_BRACE:
|
||||
// Start of a new JSON object
|
||||
this.partialObject = {}
|
||||
this.currentKey = ""
|
||||
this.parsingKey = false
|
||||
this.parsingValue = false
|
||||
state.partialObject = {}
|
||||
state.currentKey = ""
|
||||
state.parsingKey = false
|
||||
state.parsingValue = false
|
||||
break
|
||||
case TokenType.RIGHT_BRACE:
|
||||
// End of the current JSON object
|
||||
this.currentKey = ""
|
||||
this.currentValue = ""
|
||||
this.parsingKey = false
|
||||
this.parsingValue = false
|
||||
state.currentKey = ""
|
||||
state.currentValue = ""
|
||||
state.parsingKey = false
|
||||
state.parsingValue = false
|
||||
|
||||
// Finalize the object once parsing is complete
|
||||
// ;(this.assistantContentBlocks[chunkIndex] as Anthropic.ToolUseBlock).input = this.partialObject
|
||||
// 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.
|
||||
console.log("Final parsed object:", this.partialObject)
|
||||
console.log("Final parsed object:", state.partialObject)
|
||||
break
|
||||
case TokenType.STRING:
|
||||
if (!this.parsingValue && !this.parsingKey) {
|
||||
if (!state.parsingValue && !state.parsingKey) {
|
||||
// Starting to parse a key
|
||||
this.currentKey = value as string
|
||||
this.parsingKey = !!partial // if not partial, we are done parsing key
|
||||
} else if (this.parsingKey) {
|
||||
state.currentKey = value as string
|
||||
state.parsingKey = !!partial // if not partial, we are done parsing key
|
||||
} else if (state.parsingKey) {
|
||||
// Continuing to parse a key
|
||||
this.currentKey = value as string
|
||||
this.parsingKey = !!partial
|
||||
} else if (this.parsingValue) {
|
||||
state.currentKey = value as string
|
||||
state.parsingKey = !!partial
|
||||
} else if (state.parsingValue) {
|
||||
// Parsing a value
|
||||
// Accumulate partial value and update the object
|
||||
this.currentValue = value as string
|
||||
if (this.currentKey) {
|
||||
this.partialObject[this.currentKey] = this.currentValue
|
||||
state.currentValue = value as string
|
||||
if (state.currentKey) {
|
||||
state.partialObject[state.currentKey] = state.currentValue
|
||||
}
|
||||
this.parsingValue = !!partial // if not partial, complete value
|
||||
state.parsingValue = !!partial // if not partial, complete value
|
||||
}
|
||||
break
|
||||
case TokenType.COLON:
|
||||
// After a key and colon, expect a value
|
||||
if (this.currentKey !== null) {
|
||||
this.parsingValue = true
|
||||
if (state.currentKey !== null) {
|
||||
state.parsingValue = true
|
||||
}
|
||||
break
|
||||
case TokenType.COMMA:
|
||||
// Reset for the next key-value pair
|
||||
this.currentKey = ""
|
||||
this.currentValue = ""
|
||||
this.parsingKey = false
|
||||
this.parsingValue = false
|
||||
state.currentKey = ""
|
||||
state.currentValue = ""
|
||||
state.parsingKey = false
|
||||
state.parsingValue = false
|
||||
break
|
||||
default:
|
||||
console.error("Unexpected token:", token)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// Update the contentBlock with the current state of the partial object
|
||||
// Use spread operator to ensure a new object reference
|
||||
;(this.assistantContentBlocks[chunkIndex] as Anthropic.ToolUseBlock).input = {
|
||||
...this.partialObject,
|
||||
...state.partialObject,
|
||||
}
|
||||
// right brace indicates the end of the json object
|
||||
this.assistantContentBlocks[chunkIndex]!.partial = token !== TokenType.RIGHT_BRACE
|
||||
@@ -2048,53 +2031,8 @@ ${this.customInstructions.trim()}
|
||||
})
|
||||
)
|
||||
|
||||
// potentially expensive operations
|
||||
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),
|
||||
])
|
||||
|
||||
const [parsedUserContent, environmentDetails] = await this.loadContext(userContent, includeFileDetails)
|
||||
userContent = parsedUserContent
|
||||
|
||||
// add environment details as its own text block, separate from tool results
|
||||
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
|
||||
|
||||
private formatImagesIntoBlocks(images?: string[]): Anthropic.ImageBlockParam[] {
|
||||
|
||||
Reference in New Issue
Block a user