Skip to content
67 changes: 44 additions & 23 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,11 @@ export class Task extends EventEmitter<ClineEvents> {
this.isStreaming = true

try {
for await (const chunk of stream) {
const iterator = stream[Symbol.asyncIterator]()
let item = await iterator.next()
while (!item.done) {
const chunk = item.value
item = await iterator.next()
if (!chunk) {
// Sometimes chunk is undefined, no idea that can cause
// it, but this workaround seems to fix it.
Expand Down Expand Up @@ -1423,16 +1427,51 @@ export class Task extends EventEmitter<ClineEvents> {
break
}

// PREV: We need to let the request finish for openrouter to
// get generation details.
// UPDATE: It's better UX to interrupt the request at the
// cost of the API cost not being retrieved.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment calls out OpenRouter specifically, but I think if seen usage being misreported with other providers as well.

if (this.didAlreadyUseTool) {
assistantMessage +=
"\n\n[Response interrupted by a tool use result. Only one tool may be used at a time and should be placed at the end of the message.]"
break
}
}

const drainStreamInBackgroundToFindAllUsage = async () => {
let usageFound = false
while (!item.done) {
const chunk = item.value
item = await iterator.next()
if (chunk && chunk.type === "usage") {
usageFound = true
inputTokens += chunk.inputTokens
outputTokens += chunk.outputTokens
cacheWriteTokens += chunk.cacheWriteTokens ?? 0
cacheReadTokens += chunk.cacheReadTokens ?? 0
totalCost = chunk.totalCost
}
}
if (usageFound) {
updateApiReqMsg()
}
if (inputTokens > 0 || outputTokens > 0 || cacheWriteTokens > 0 || cacheReadTokens > 0) {
TelemetryService.instance.captureLlmCompletion(this.taskId, {
inputTokens,
outputTokens,
cacheWriteTokens,
cacheReadTokens,
cost:
totalCost ??
calculateApiCostAnthropic(
this.api.getModel().info,
inputTokens,
outputTokens,
cacheWriteTokens,
cacheReadTokens,
),
})
} else {
console.warn(`Suspicious: request ${lastApiReqIndex} is complete, but no usage info was found.`)
}
}
drainStreamInBackgroundToFindAllUsage() // no await so it runs in the background
} catch (error) {
// Abandoned happens when extension is no longer waiting for the
// Cline instance to finish aborting (error is thrown here when
Expand Down Expand Up @@ -1466,24 +1505,6 @@ export class Task extends EventEmitter<ClineEvents> {
this.isStreaming = false
}

if (inputTokens > 0 || outputTokens > 0 || cacheWriteTokens > 0 || cacheReadTokens > 0) {
TelemetryService.instance.captureLlmCompletion(this.taskId, {
inputTokens,
outputTokens,
cacheWriteTokens,
cacheReadTokens,
cost:
totalCost ??
calculateApiCostAnthropic(
this.api.getModel().info,
inputTokens,
outputTokens,
cacheWriteTokens,
cacheReadTokens,
),
})
}

// Need to call here in case the stream was aborted.
if (this.abort || this.abandoned) {
throw new Error(`[RooCode#recursivelyMakeRooRequests] task ${this.taskId}.${this.instanceId} aborted`)
Expand Down