Skip to content

Commit 0bce737

Browse files
committed
feat: Support streaming conversion of json to xml
1 parent 455a523 commit 0bce737

File tree

7 files changed

+757
-366
lines changed

7 files changed

+757
-366
lines changed

src/core/task/Task.ts

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ import { getMessagesSinceLastSummary, summarizeConversation } from "../condense"
101101
import { maybeRemoveImageBlocks } from "../../api/transform/image-cleaning"
102102
import { restoreTodoListForTask } from "../tools/updateTodoListTool"
103103
import { AutoApprovalHandler } from "./AutoApprovalHandler"
104-
import { handleOpenaiToolCall } from "./tool-call-helper"
104+
import { StreamingToolCallProcessor, handleOpenaiToolCallStreaming } from "./tool-call-helper"
105105
import { ToolArgs } from "../prompts/tools/types"
106106

107107
const MAX_EXPONENTIAL_BACKOFF_SECONDS = 600 // 10 minutes
@@ -236,6 +236,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
236236
consecutiveMistakeCountForApplyDiff: Map<string, number> = new Map()
237237
toolUsage: ToolUsage = {}
238238

239+
// Streaming Tool Call Processing
240+
streamingToolCallProcessor: StreamingToolCallProcessor = new StreamingToolCallProcessor()
241+
239242
// Checkpoints
240243
enableCheckpoints: boolean
241244
checkpointService?: RepoPerTaskCheckpointService
@@ -1564,6 +1567,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
15641567
this.assistantMessageParser.reset()
15651568
}
15661569

1570+
// Reset streaming tool call processor
1571+
this.streamingToolCallProcessor.reset()
1572+
15671573
await this.diffViewProvider.reset()
15681574

15691575
// Yields only if the first chunk is successful, otherwise will
@@ -1572,7 +1578,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
15721578
const stream = this.attemptApiRequest()
15731579
let assistantMessage = ""
15741580
let reasoningMessage = ""
1575-
let accumulatedToolCalls: any[] = []
15761581
this.isStreaming = true
15771582

15781583
try {
@@ -1595,13 +1600,25 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
15951600
cacheReadTokens += chunk.cacheReadTokens ?? 0
15961601
totalCost = chunk.totalCost
15971602
break
1598-
case "text": {
1599-
assistantMessage += chunk.text
1603+
case "text":
1604+
case "tool_call": {
1605+
let chunkContent
1606+
if (chunk.type == "tool_call") {
1607+
chunkContent =
1608+
handleOpenaiToolCallStreaming(
1609+
this.streamingToolCallProcessor,
1610+
chunk.toolCalls,
1611+
chunk.toolCallType,
1612+
) ?? ""
1613+
} else {
1614+
chunkContent = chunk.text
1615+
}
1616+
assistantMessage += chunkContent
16001617

16011618
// Parse raw assistant message chunk into content blocks.
16021619
const prevLength = this.assistantMessageContent.length
16031620
if (this.isAssistantMessageParserEnabled && this.assistantMessageParser) {
1604-
this.assistantMessageContent = this.assistantMessageParser.processChunk(chunk.text)
1621+
this.assistantMessageContent = this.assistantMessageParser.processChunk(chunkContent)
16051622
} else {
16061623
// Use the old parsing method when experiment is disabled
16071624
this.assistantMessageContent = parseAssistantMessage(assistantMessage)
@@ -1617,34 +1634,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
16171634
presentAssistantMessage(this)
16181635
break
16191636
}
1620-
case "tool_call": {
1621-
if (chunk.toolCallType === "openai") {
1622-
const xmlContent = handleOpenaiToolCall(accumulatedToolCalls, chunk)
1623-
if (xmlContent) {
1624-
assistantMessage += xmlContent
1625-
1626-
// Parse raw assistant message chunk into content blocks.
1627-
const prevLength = this.assistantMessageContent.length
1628-
if (this.isAssistantMessageParserEnabled && this.assistantMessageParser) {
1629-
this.assistantMessageContent =
1630-
this.assistantMessageParser.processChunk(xmlContent)
1631-
} else {
1632-
// Use the old parsing method when experiment is disabled
1633-
this.assistantMessageContent = parseAssistantMessage(assistantMessage)
1634-
}
1635-
1636-
if (this.assistantMessageContent.length > prevLength) {
1637-
// New content we need to present, reset to
1638-
// false in case previous content set this to true.
1639-
this.userMessageContentReady = false
1640-
}
1641-
1642-
// Present content to user.
1643-
presentAssistantMessage(this)
1644-
break
1645-
}
1646-
}
1647-
}
16481637
}
16491638

16501639
if (this.abort) {
@@ -1760,6 +1749,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
17601749
}
17611750
// When using old parser, no finalization needed - parsing already happened during streaming
17621751

1752+
this.streamingToolCallProcessor.reset()
1753+
17631754
if (partialBlocks.length > 0) {
17641755
// If there is content to update then it will complete and
17651756
// update `this.userMessageContentReady` to true, which we
@@ -1917,7 +1908,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
19171908
{
19181909
maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
19191910
todoListEnabled: apiConfiguration?.todoListEnabled ?? true,
1920-
toolCallEnabled: apiConfiguration?.toolCallEnabled ?? false,
1911+
toolCallEnabled:
1912+
(apiConfiguration?.toolCallEnabled ?? false) && supportToolCall(apiConfiguration?.apiProvider),
19211913
useAgentRules: vscode.workspace.getConfiguration("roo-cline").get<boolean>("useAgentRules") ?? true,
19221914
},
19231915
)
@@ -2111,7 +2103,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
21112103
...{
21122104
maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
21132105
todoListEnabled: apiConfiguration?.todoListEnabled ?? true,
2114-
toolCallEnabled: apiConfiguration?.toolCallEnabled ?? false,
2106+
toolCallEnabled:
2107+
(apiConfiguration?.toolCallEnabled ?? false) &&
2108+
supportToolCall(apiConfiguration?.apiProvider),
21152109
useAgentRules:
21162110
vscode.workspace.getConfiguration("roo-cline").get<boolean>("useAgentRules") ?? true,
21172111
},

0 commit comments

Comments
 (0)