mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
refactor: break handler classes into separate files
- Extract TextContentHandler into TextContentHandler.ts - Extract ParameterHandler into ParameterHandler.ts - Extract ToolUseHandler into ToolUseHandler.ts - Create types.ts for shared type definitions - Update StreamingParser to import from separate files - Update index.ts exports to reference correct files - Update README.md to reflect new file structure - Maintain backward compatibility and streaming behavior - All tests pass, multi-contributor friendly structure
This commit is contained in:
parent
773075e66c
commit
1d97e1f5bc
6 changed files with 169 additions and 157 deletions
22
src/core/assistant-message/directives/ParameterHandler.ts
Normal file
22
src/core/assistant-message/directives/ParameterHandler.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { ParsingState } from "./types"
|
||||
|
||||
export class ParameterHandler {
|
||||
static handleParameter(state: ParsingState): boolean {
|
||||
if (!state.currentToolUse || !state.currentParamName) return false
|
||||
|
||||
const currentParamValue = state.accumulator.slice(state.currentParamValueStartIndex)
|
||||
const paramClosingTag = `</${state.currentParamName}>`
|
||||
|
||||
if (currentParamValue.endsWith(paramClosingTag)) {
|
||||
// End of param value.
|
||||
state.currentToolUse.params[state.currentParamName] = currentParamValue
|
||||
.slice(0, -paramClosingTag.length)
|
||||
.trim()
|
||||
state.currentParamName = undefined
|
||||
return true
|
||||
} else {
|
||||
// Partial param value is accumulating.
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,158 +1,7 @@
|
|||
import { type ToolName, toolNames } from "@roo-code/types"
|
||||
import { TextContent, ToolUse, ToolParamName, toolParamNames } from "../../../shared/tools"
|
||||
|
||||
// Type aliases for directive parsing
|
||||
export type TextDirective = TextContent
|
||||
export type ToolDirective = ToolUse
|
||||
export type Directive = TextDirective | ToolDirective
|
||||
|
||||
export interface ParsingState {
|
||||
contentBlocks: Directive[]
|
||||
currentTextContent?: TextDirective
|
||||
currentTextContentStartIndex: number
|
||||
currentToolUse?: ToolDirective
|
||||
currentToolUseStartIndex: number
|
||||
currentParamName?: ToolParamName
|
||||
currentParamValueStartIndex: number
|
||||
accumulator: string
|
||||
}
|
||||
|
||||
export class TextContentHandler {
|
||||
static handleTextContent(state: ParsingState, currentIndex: number, didStartToolUse: boolean): void {
|
||||
if (!didStartToolUse) {
|
||||
// No tool use, so it must be text either at the beginning or between tools.
|
||||
if (state.currentTextContent === undefined) {
|
||||
state.currentTextContentStartIndex = currentIndex
|
||||
}
|
||||
|
||||
state.currentTextContent = {
|
||||
type: "text",
|
||||
content: state.accumulator.slice(state.currentTextContentStartIndex).trim(),
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static finalizeTextContent(state: ParsingState, toolUseOpeningTag: string): void {
|
||||
if (state.currentTextContent) {
|
||||
state.currentTextContent.partial = false
|
||||
|
||||
// Remove the partially accumulated tool use tag from the end of text (<tool).
|
||||
state.currentTextContent.content = state.currentTextContent.content
|
||||
.slice(0, -toolUseOpeningTag.slice(0, -1).length)
|
||||
.trim()
|
||||
|
||||
state.contentBlocks.push(state.currentTextContent)
|
||||
state.currentTextContent = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ToolUseHandler {
|
||||
static checkForToolStart(state: ParsingState): boolean {
|
||||
let didStartToolUse = false
|
||||
const possibleToolUseOpeningTags = toolNames.map((name) => `<${name}>`)
|
||||
|
||||
for (const toolUseOpeningTag of possibleToolUseOpeningTags) {
|
||||
if (state.accumulator.endsWith(toolUseOpeningTag)) {
|
||||
// Start of a new tool use.
|
||||
state.currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: toolUseOpeningTag.slice(1, -1) as ToolName,
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
|
||||
state.currentToolUseStartIndex = state.accumulator.length
|
||||
|
||||
// This also indicates the end of the current text content.
|
||||
TextContentHandler.finalizeTextContent(state, toolUseOpeningTag)
|
||||
|
||||
didStartToolUse = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return didStartToolUse
|
||||
}
|
||||
|
||||
static handleToolUse(state: ParsingState): boolean {
|
||||
if (!state.currentToolUse) return false
|
||||
|
||||
const currentToolValue = state.accumulator.slice(state.currentToolUseStartIndex)
|
||||
const toolUseClosingTag = `</${state.currentToolUse.name}>`
|
||||
|
||||
if (currentToolValue.endsWith(toolUseClosingTag)) {
|
||||
// End of a tool use.
|
||||
state.currentToolUse.partial = false
|
||||
state.contentBlocks.push(state.currentToolUse)
|
||||
state.currentToolUse = undefined
|
||||
return true
|
||||
} else {
|
||||
this.handleParameterParsing(state)
|
||||
this.handleSpecialCases(state)
|
||||
return true // Continue processing
|
||||
}
|
||||
}
|
||||
|
||||
private static handleParameterParsing(state: ParsingState): void {
|
||||
const possibleParamOpeningTags = toolParamNames.map((name) => `<${name}>`)
|
||||
for (const paramOpeningTag of possibleParamOpeningTags) {
|
||||
if (state.accumulator.endsWith(paramOpeningTag)) {
|
||||
// Start of a new parameter.
|
||||
state.currentParamName = paramOpeningTag.slice(1, -1) as ToolParamName
|
||||
state.currentParamValueStartIndex = state.accumulator.length
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static handleSpecialCases(state: ParsingState): void {
|
||||
if (!state.currentToolUse) return
|
||||
|
||||
// Special case for write_to_file where file contents could
|
||||
// contain the closing tag, in which case the param would have
|
||||
// closed and we end up with the rest of the file contents here.
|
||||
// To work around this, we get the string between the starting
|
||||
// content tag and the LAST content tag.
|
||||
const contentParamName: ToolParamName = "content"
|
||||
|
||||
if (state.currentToolUse.name === "write_to_file" && state.accumulator.endsWith(`</${contentParamName}>`)) {
|
||||
const toolContent = state.accumulator.slice(state.currentToolUseStartIndex)
|
||||
const contentStartTag = `<${contentParamName}>`
|
||||
const contentEndTag = `</${contentParamName}>`
|
||||
const contentStartIndex = toolContent.indexOf(contentStartTag) + contentStartTag.length
|
||||
const contentEndIndex = toolContent.lastIndexOf(contentEndTag)
|
||||
|
||||
if (contentStartIndex !== -1 && contentEndIndex !== -1 && contentEndIndex > contentStartIndex) {
|
||||
state.currentToolUse.params[contentParamName] = toolContent
|
||||
.slice(contentStartIndex, contentEndIndex)
|
||||
.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ParameterHandler {
|
||||
static handleParameter(state: ParsingState): boolean {
|
||||
if (!state.currentToolUse || !state.currentParamName) return false
|
||||
|
||||
const currentParamValue = state.accumulator.slice(state.currentParamValueStartIndex)
|
||||
const paramClosingTag = `</${state.currentParamName}>`
|
||||
|
||||
if (currentParamValue.endsWith(paramClosingTag)) {
|
||||
// End of param value.
|
||||
state.currentToolUse.params[state.currentParamName] = currentParamValue
|
||||
.slice(0, -paramClosingTag.length)
|
||||
.trim()
|
||||
state.currentParamName = undefined
|
||||
return true
|
||||
} else {
|
||||
// Partial param value is accumulating.
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Directive, ParsingState } from "./types"
|
||||
import { TextContentHandler } from "./TextContentHandler"
|
||||
import { ToolUseHandler } from "./ToolUseHandler"
|
||||
import { ParameterHandler } from "./ParameterHandler"
|
||||
|
||||
export class StreamingParser {
|
||||
static parse(assistantMessage: string): Directive[] {
|
||||
|
|
|
|||
32
src/core/assistant-message/directives/TextContentHandler.ts
Normal file
32
src/core/assistant-message/directives/TextContentHandler.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { ParsingState } from "./types"
|
||||
|
||||
export class TextContentHandler {
|
||||
static handleTextContent(state: ParsingState, currentIndex: number, didStartToolUse: boolean): void {
|
||||
if (!didStartToolUse) {
|
||||
// No tool use, so it must be text either at the beginning or between tools.
|
||||
if (state.currentTextContent === undefined) {
|
||||
state.currentTextContentStartIndex = currentIndex
|
||||
}
|
||||
|
||||
state.currentTextContent = {
|
||||
type: "text",
|
||||
content: state.accumulator.slice(state.currentTextContentStartIndex).trim(),
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static finalizeTextContent(state: ParsingState, toolUseOpeningTag: string): void {
|
||||
if (state.currentTextContent) {
|
||||
state.currentTextContent.partial = false
|
||||
|
||||
// Remove the partially accumulated tool use tag from the end of text (<tool).
|
||||
state.currentTextContent.content = state.currentTextContent.content
|
||||
.slice(0, -toolUseOpeningTag.slice(0, -1).length)
|
||||
.trim()
|
||||
|
||||
state.contentBlocks.push(state.currentTextContent)
|
||||
state.currentTextContent = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/core/assistant-message/directives/ToolUseHandler.ts
Normal file
89
src/core/assistant-message/directives/ToolUseHandler.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { type ToolName, toolNames } from "@roo-code/types"
|
||||
import { ToolParamName, toolParamNames } from "../../../shared/tools"
|
||||
import { ParsingState } from "./types"
|
||||
import { TextContentHandler } from "./TextContentHandler"
|
||||
|
||||
export class ToolUseHandler {
|
||||
static checkForToolStart(state: ParsingState): boolean {
|
||||
let didStartToolUse = false
|
||||
const possibleToolUseOpeningTags = toolNames.map((name) => `<${name}>`)
|
||||
|
||||
for (const toolUseOpeningTag of possibleToolUseOpeningTags) {
|
||||
if (state.accumulator.endsWith(toolUseOpeningTag)) {
|
||||
// Start of a new tool use.
|
||||
state.currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: toolUseOpeningTag.slice(1, -1) as ToolName,
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
|
||||
state.currentToolUseStartIndex = state.accumulator.length
|
||||
|
||||
// This also indicates the end of the current text content.
|
||||
TextContentHandler.finalizeTextContent(state, toolUseOpeningTag)
|
||||
|
||||
didStartToolUse = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return didStartToolUse
|
||||
}
|
||||
|
||||
static handleToolUse(state: ParsingState): boolean {
|
||||
if (!state.currentToolUse) return false
|
||||
|
||||
const currentToolValue = state.accumulator.slice(state.currentToolUseStartIndex)
|
||||
const toolUseClosingTag = `</${state.currentToolUse.name}>`
|
||||
|
||||
if (currentToolValue.endsWith(toolUseClosingTag)) {
|
||||
// End of a tool use.
|
||||
state.currentToolUse.partial = false
|
||||
state.contentBlocks.push(state.currentToolUse)
|
||||
state.currentToolUse = undefined
|
||||
return true
|
||||
} else {
|
||||
this.handleParameterParsing(state)
|
||||
this.handleSpecialCases(state)
|
||||
return true // Continue processing
|
||||
}
|
||||
}
|
||||
|
||||
private static handleParameterParsing(state: ParsingState): void {
|
||||
const possibleParamOpeningTags = toolParamNames.map((name) => `<${name}>`)
|
||||
for (const paramOpeningTag of possibleParamOpeningTags) {
|
||||
if (state.accumulator.endsWith(paramOpeningTag)) {
|
||||
// Start of a new parameter.
|
||||
state.currentParamName = paramOpeningTag.slice(1, -1) as ToolParamName
|
||||
state.currentParamValueStartIndex = state.accumulator.length
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static handleSpecialCases(state: ParsingState): void {
|
||||
if (!state.currentToolUse) return
|
||||
|
||||
// Special case for write_to_file where file contents could
|
||||
// contain the closing tag, in which case the param would have
|
||||
// closed and we end up with the rest of the file contents here.
|
||||
// To work around this, we get the string between the starting
|
||||
// content tag and the LAST content tag.
|
||||
const contentParamName: ToolParamName = "content"
|
||||
|
||||
if (state.currentToolUse.name === "write_to_file" && state.accumulator.endsWith(`</${contentParamName}>`)) {
|
||||
const toolContent = state.accumulator.slice(state.currentToolUseStartIndex)
|
||||
const contentStartTag = `<${contentParamName}>`
|
||||
const contentEndTag = `</${contentParamName}>`
|
||||
const contentStartIndex = toolContent.indexOf(contentStartTag) + contentStartTag.length
|
||||
const contentEndIndex = toolContent.lastIndexOf(contentEndTag)
|
||||
|
||||
if (contentStartIndex !== -1 && contentEndIndex !== -1 && contentEndIndex > contentStartIndex) {
|
||||
state.currentToolUse.params[contentParamName] = toolContent
|
||||
.slice(contentStartIndex, contentEndIndex)
|
||||
.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,5 @@
|
|||
export { StreamingParser, TextContentHandler, ToolUseHandler, ParameterHandler } from "./StreamingParser"
|
||||
export type { TextDirective, ToolDirective, Directive, ParsingState } from "./StreamingParser"
|
||||
export { StreamingParser } from "./StreamingParser"
|
||||
export { TextContentHandler } from "./TextContentHandler"
|
||||
export { ToolUseHandler } from "./ToolUseHandler"
|
||||
export { ParameterHandler } from "./ParameterHandler"
|
||||
export type { TextDirective, ToolDirective, Directive, ParsingState } from "./types"
|
||||
|
|
|
|||
17
src/core/assistant-message/directives/types.ts
Normal file
17
src/core/assistant-message/directives/types.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { TextContent, ToolUse, ToolParamName } from "../../../shared/tools"
|
||||
|
||||
// Type aliases for directive parsing
|
||||
export type TextDirective = TextContent
|
||||
export type ToolDirective = ToolUse
|
||||
export type Directive = TextDirective | ToolDirective
|
||||
|
||||
export interface ParsingState {
|
||||
contentBlocks: Directive[]
|
||||
currentTextContent?: TextDirective
|
||||
currentTextContentStartIndex: number
|
||||
currentToolUse?: ToolDirective
|
||||
currentToolUseStartIndex: number
|
||||
currentParamName?: ToolParamName
|
||||
currentParamValueStartIndex: number
|
||||
accumulator: string
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue