mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Add some functionality to @roo-code/core for the cli (#10584)
This commit is contained in:
parent
3171ffc809
commit
a4eb15b5a8
60 changed files with 1343 additions and 632 deletions
|
|
@ -3,7 +3,11 @@
|
|||
"description": "Platform agnostic core functionality for Roo Code.",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"exports": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./cli": "./src/cli.ts",
|
||||
"./browser": "./src/browser.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint src --ext=ts --max-warnings=0",
|
||||
"check-types": "tsc --noEmit",
|
||||
|
|
|
|||
6
packages/core/src/browser.ts
Normal file
6
packages/core/src/browser.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Browser-safe exports for the core package. These can safely be used
|
||||
* in browser environments like `webview-ui`.
|
||||
*/
|
||||
|
||||
export * from "./message-utils/index.js"
|
||||
6
packages/core/src/cli.ts
Normal file
6
packages/core/src/cli.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Cli-safe exports for the core package.
|
||||
*/
|
||||
|
||||
export * from "./debug-log/index.js"
|
||||
export * from "./message-utils/index.js"
|
||||
91
packages/core/src/debug-log/index.ts
Normal file
91
packages/core/src/debug-log/index.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* File-based debug logging utility
|
||||
*
|
||||
* This writes logs to ~/.roo/cli-debug.log, avoiding stdout/stderr
|
||||
* which would break TUI applications. The log format is timestamped JSON.
|
||||
*
|
||||
* Usage:
|
||||
* import { debugLog, DebugLogger } from "@roo-code/core/debug-log"
|
||||
*
|
||||
* // Simple logging
|
||||
* debugLog("handleModeSwitch", { mode: newMode, configId })
|
||||
*
|
||||
* // Or create a named logger for a component
|
||||
* const log = new DebugLogger("ClineProvider")
|
||||
* log.info("handleModeSwitch", { mode: newMode })
|
||||
*/
|
||||
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
|
||||
const DEBUG_LOG_PATH = path.join(os.homedir(), ".roo", "cli-debug.log")
|
||||
|
||||
/**
|
||||
* Simple file-based debug log function.
|
||||
* Writes timestamped entries to ~/.roo/cli-debug.log
|
||||
*/
|
||||
export function debugLog(message: string, data?: unknown): void {
|
||||
try {
|
||||
const logDir = path.dirname(DEBUG_LOG_PATH)
|
||||
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true })
|
||||
}
|
||||
|
||||
const timestamp = new Date().toISOString()
|
||||
|
||||
const entry = data
|
||||
? `[${timestamp}] ${message}: ${JSON.stringify(data, null, 2)}\n`
|
||||
: `[${timestamp}] ${message}\n`
|
||||
|
||||
fs.appendFileSync(DEBUG_LOG_PATH, entry)
|
||||
} catch {
|
||||
// NO-OP - don't let logging errors break functionality
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug logger with component context.
|
||||
* Prefixes all messages with the component name.
|
||||
*/
|
||||
export class DebugLogger {
|
||||
private component: string
|
||||
|
||||
constructor(component: string) {
|
||||
this.component = component
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a debug message with optional data
|
||||
*/
|
||||
debug(message: string, data?: unknown): void {
|
||||
debugLog(`[${this.component}] ${message}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for debug
|
||||
*/
|
||||
info(message: string, data?: unknown): void {
|
||||
this.debug(message, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning
|
||||
*/
|
||||
warn(message: string, data?: unknown): void {
|
||||
debugLog(`[${this.component}] WARN: ${message}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*/
|
||||
error(message: string, data?: unknown): void {
|
||||
debugLog(`[${this.component}] ERROR: ${message}`, data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-configured logger for provider/mode debugging
|
||||
*/
|
||||
export const providerDebugLog = new DebugLogger("ProviderSettings")
|
||||
|
|
@ -1 +1,3 @@
|
|||
export * from "./custom-tools/index.js"
|
||||
export * from "./debug-log/index.js"
|
||||
export * from "./message-utils/index.js"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
// npx vitest run packages/core/src/message-utils/__tests__/consolidateApiRequests.spec.ts
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { consolidateApiRequests } from "../consolidateApiRequests.js"
|
||||
|
||||
describe("consolidateApiRequests", () => {
|
||||
// Helper function to create a basic api_req_started message
|
||||
const createApiReqStarted = (ts: number, data: Record<string, unknown> = {}): ClineMessage => ({
|
||||
ts,
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: JSON.stringify(data),
|
||||
})
|
||||
|
||||
// Helper function to create a basic api_req_finished message
|
||||
const createApiReqFinished = (ts: number, data: Record<string, unknown> = {}): ClineMessage => ({
|
||||
ts,
|
||||
type: "say",
|
||||
say: "api_req_finished",
|
||||
text: JSON.stringify(data),
|
||||
})
|
||||
|
||||
// Helper function to create a regular text message
|
||||
const createTextMessage = (ts: number, text: string): ClineMessage => ({
|
||||
ts,
|
||||
type: "say",
|
||||
say: "text",
|
||||
text,
|
||||
})
|
||||
|
||||
it("should consolidate a matching pair of api_req_started and api_req_finished messages", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
createApiReqStarted(1000, { request: "GET /api/data" }),
|
||||
createApiReqFinished(1001, { cost: 0.005 }),
|
||||
]
|
||||
|
||||
const result = consolidateApiRequests(messages)
|
||||
|
||||
expect(result.length).toBe(1)
|
||||
expect(result[0]!.say).toBe("api_req_started")
|
||||
|
||||
const parsedText = JSON.parse(result[0]!.text || "{}")
|
||||
expect(parsedText.request).toBe("GET /api/data")
|
||||
expect(parsedText.cost).toBe(0.005)
|
||||
})
|
||||
|
||||
it("should handle messages with no api_req pairs", () => {
|
||||
const messages: ClineMessage[] = [createTextMessage(1000, "Hello"), createTextMessage(1001, "World")]
|
||||
|
||||
const result = consolidateApiRequests(messages)
|
||||
|
||||
expect(result).toEqual(messages)
|
||||
})
|
||||
|
||||
it("should handle empty messages array", () => {
|
||||
const result = consolidateApiRequests([])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it("should handle single message array", () => {
|
||||
const messages: ClineMessage[] = [createTextMessage(1000, "Hello")]
|
||||
const result = consolidateApiRequests(messages)
|
||||
expect(result).toEqual(messages)
|
||||
})
|
||||
|
||||
it("should preserve non-api messages in the result", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
createTextMessage(1000, "Before"),
|
||||
createApiReqStarted(1001, { request: "test" }),
|
||||
createApiReqFinished(1002, { cost: 0.01 }),
|
||||
createTextMessage(1003, "After"),
|
||||
]
|
||||
|
||||
const result = consolidateApiRequests(messages)
|
||||
|
||||
expect(result.length).toBe(3)
|
||||
expect(result[0]!.text).toBe("Before")
|
||||
expect(result[1]!.say).toBe("api_req_started")
|
||||
expect(result[2]!.text).toBe("After")
|
||||
})
|
||||
|
||||
it("should handle multiple api_req pairs", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
createApiReqStarted(1000, { request: "first" }),
|
||||
createApiReqFinished(1001, { cost: 0.01 }),
|
||||
createApiReqStarted(1002, { request: "second" }),
|
||||
createApiReqFinished(1003, { cost: 0.02 }),
|
||||
]
|
||||
|
||||
const result = consolidateApiRequests(messages)
|
||||
|
||||
expect(result.length).toBe(2)
|
||||
expect(JSON.parse(result[0]!.text || "{}").request).toBe("first")
|
||||
expect(JSON.parse(result[1]!.text || "{}").request).toBe("second")
|
||||
})
|
||||
|
||||
it("should handle orphan api_req_started without finish", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
createApiReqStarted(1000, { request: "orphan" }),
|
||||
createTextMessage(1001, "Text"),
|
||||
]
|
||||
|
||||
const result = consolidateApiRequests(messages)
|
||||
|
||||
expect(result.length).toBe(2)
|
||||
expect(result[0]!.say).toBe("api_req_started")
|
||||
expect(JSON.parse(result[0]!.text || "{}").request).toBe("orphan")
|
||||
})
|
||||
|
||||
it("should handle invalid JSON in message text", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{ ts: 1000, type: "say", say: "api_req_started", text: "invalid json" },
|
||||
createApiReqFinished(1001, { cost: 0.01 }),
|
||||
]
|
||||
|
||||
const result = consolidateApiRequests(messages)
|
||||
|
||||
// Should still consolidate, merging what it can
|
||||
expect(result.length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
// npx vitest run packages/core/src/message-utils/__tests__/consolidateCommands.spec.ts
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { consolidateCommands, COMMAND_OUTPUT_STRING } from "../consolidateCommands.js"
|
||||
|
||||
describe("consolidateCommands", () => {
|
||||
describe("command sequences", () => {
|
||||
it("should consolidate command and command_output messages", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{ type: "ask", ask: "command", text: "ls", ts: 1000 },
|
||||
{ type: "ask", ask: "command_output", text: "file1.txt", ts: 1001 },
|
||||
{ type: "ask", ask: "command_output", text: "file2.txt", ts: 1002 },
|
||||
]
|
||||
|
||||
const result = consolidateCommands(messages)
|
||||
|
||||
expect(result.length).toBe(1)
|
||||
expect(result[0]!.ask).toBe("command")
|
||||
expect(result[0]!.text).toBe(`ls\n${COMMAND_OUTPUT_STRING}file1.txt\nfile2.txt`)
|
||||
})
|
||||
|
||||
it("should handle multiple command sequences", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{ type: "ask", ask: "command", text: "ls", ts: 1000 },
|
||||
{ type: "ask", ask: "command_output", text: "output1", ts: 1001 },
|
||||
{ type: "ask", ask: "command", text: "pwd", ts: 1002 },
|
||||
{ type: "ask", ask: "command_output", text: "output2", ts: 1003 },
|
||||
]
|
||||
|
||||
const result = consolidateCommands(messages)
|
||||
|
||||
expect(result.length).toBe(2)
|
||||
expect(result[0]!.text).toBe(`ls\n${COMMAND_OUTPUT_STRING}output1`)
|
||||
expect(result[1]!.text).toBe(`pwd\n${COMMAND_OUTPUT_STRING}output2`)
|
||||
})
|
||||
|
||||
it("should handle command without output", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{ type: "ask", ask: "command", text: "ls", ts: 1000 },
|
||||
{ type: "say", say: "text", text: "some text", ts: 1001 },
|
||||
]
|
||||
|
||||
const result = consolidateCommands(messages)
|
||||
|
||||
expect(result.length).toBe(2)
|
||||
expect(result[0]!.ask).toBe("command")
|
||||
expect(result[0]!.text).toBe("ls")
|
||||
expect(result[1]!.say).toBe("text")
|
||||
})
|
||||
|
||||
it("should handle duplicate outputs (ask and say with same text)", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{ type: "ask", ask: "command", text: "ls", ts: 1000 },
|
||||
{ type: "ask", ask: "command_output", text: "same output", ts: 1001 },
|
||||
{ type: "say", say: "command_output", text: "same output", ts: 1002 },
|
||||
]
|
||||
|
||||
const result = consolidateCommands(messages)
|
||||
|
||||
expect(result.length).toBe(1)
|
||||
expect(result[0]!.text).toBe(`ls\n${COMMAND_OUTPUT_STRING}same output`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("MCP server sequences", () => {
|
||||
it("should consolidate use_mcp_server and mcp_server_response messages", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "use_mcp_server",
|
||||
text: JSON.stringify({ server: "test", tool: "myTool" }),
|
||||
ts: 1000,
|
||||
},
|
||||
{ type: "say", say: "mcp_server_response", text: "response data", ts: 1001 },
|
||||
]
|
||||
|
||||
const result = consolidateCommands(messages)
|
||||
|
||||
expect(result.length).toBe(1)
|
||||
expect(result[0]!.ask).toBe("use_mcp_server")
|
||||
const parsed = JSON.parse(result[0]!.text || "{}")
|
||||
expect(parsed.server).toBe("test")
|
||||
expect(parsed.response).toBe("response data")
|
||||
})
|
||||
|
||||
it("should handle MCP request without response", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "use_mcp_server",
|
||||
text: JSON.stringify({ server: "test" }),
|
||||
ts: 1000,
|
||||
},
|
||||
]
|
||||
|
||||
const result = consolidateCommands(messages)
|
||||
|
||||
expect(result.length).toBe(1)
|
||||
expect(result[0]!.ask).toBe("use_mcp_server")
|
||||
})
|
||||
|
||||
it("should handle multiple MCP responses", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "use_mcp_server",
|
||||
text: JSON.stringify({ server: "test" }),
|
||||
ts: 1000,
|
||||
},
|
||||
{ type: "say", say: "mcp_server_response", text: "response1", ts: 1001 },
|
||||
{ type: "say", say: "mcp_server_response", text: "response2", ts: 1002 },
|
||||
]
|
||||
|
||||
const result = consolidateCommands(messages)
|
||||
|
||||
expect(result.length).toBe(1)
|
||||
const parsed = JSON.parse(result[0]!.text || "{}")
|
||||
expect(parsed.response).toBe("response1\nresponse2")
|
||||
})
|
||||
})
|
||||
|
||||
describe("mixed messages", () => {
|
||||
it("should preserve non-command, non-MCP messages", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{ type: "say", say: "text", text: "before", ts: 1000 },
|
||||
{ type: "ask", ask: "command", text: "ls", ts: 1001 },
|
||||
{ type: "ask", ask: "command_output", text: "output", ts: 1002 },
|
||||
{ type: "say", say: "text", text: "after", ts: 1003 },
|
||||
]
|
||||
|
||||
const result = consolidateCommands(messages)
|
||||
|
||||
expect(result.length).toBe(3)
|
||||
expect(result[0]!.text).toBe("before")
|
||||
expect(result[1]!.text).toBe(`ls\n${COMMAND_OUTPUT_STRING}output`)
|
||||
expect(result[2]!.text).toBe("after")
|
||||
})
|
||||
|
||||
it("should handle empty array", () => {
|
||||
const result = consolidateCommands([])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
// npx vitest run packages/core/src/message-utils/__tests__/consolidateTokenUsage.spec.ts
|
||||
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { consolidateTokenUsage, hasTokenUsageChanged, hasToolUsageChanged } from "../consolidateTokenUsage.js"
|
||||
|
||||
describe("consolidateTokenUsage", () => {
|
||||
// Helper function to create a basic api_req_started message
|
||||
const createApiReqMessage = (
|
||||
ts: number,
|
||||
data: {
|
||||
tokensIn?: number
|
||||
tokensOut?: number
|
||||
cacheWrites?: number
|
||||
cacheReads?: number
|
||||
cost?: number
|
||||
},
|
||||
): ClineMessage => ({
|
||||
ts,
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
text: JSON.stringify(data),
|
||||
})
|
||||
|
||||
describe("basic token accumulation", () => {
|
||||
it("should accumulate tokens from a single message", () => {
|
||||
const messages: ClineMessage[] = [createApiReqMessage(1000, { tokensIn: 100, tokensOut: 50, cost: 0.01 })]
|
||||
|
||||
const result = consolidateTokenUsage(messages)
|
||||
|
||||
expect(result.totalTokensIn).toBe(100)
|
||||
expect(result.totalTokensOut).toBe(50)
|
||||
expect(result.totalCost).toBe(0.01)
|
||||
})
|
||||
|
||||
it("should accumulate tokens from multiple messages", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
createApiReqMessage(1000, { tokensIn: 100, tokensOut: 50, cost: 0.01 }),
|
||||
createApiReqMessage(1001, { tokensIn: 200, tokensOut: 100, cost: 0.02 }),
|
||||
]
|
||||
|
||||
const result = consolidateTokenUsage(messages)
|
||||
|
||||
expect(result.totalTokensIn).toBe(300)
|
||||
expect(result.totalTokensOut).toBe(150)
|
||||
expect(result.totalCost).toBeCloseTo(0.03)
|
||||
})
|
||||
|
||||
it("should handle cache writes and reads", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
createApiReqMessage(1000, { tokensIn: 100, tokensOut: 50, cacheWrites: 500, cacheReads: 200 }),
|
||||
]
|
||||
|
||||
const result = consolidateTokenUsage(messages)
|
||||
|
||||
expect(result.totalCacheWrites).toBe(500)
|
||||
expect(result.totalCacheReads).toBe(200)
|
||||
})
|
||||
|
||||
it("should handle empty messages array", () => {
|
||||
const result = consolidateTokenUsage([])
|
||||
|
||||
expect(result.totalTokensIn).toBe(0)
|
||||
expect(result.totalTokensOut).toBe(0)
|
||||
expect(result.totalCost).toBe(0)
|
||||
expect(result.contextTokens).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("context tokens calculation", () => {
|
||||
it("should calculate context tokens from the last API request", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
createApiReqMessage(1000, { tokensIn: 100, tokensOut: 50 }),
|
||||
createApiReqMessage(1001, { tokensIn: 200, tokensOut: 100 }),
|
||||
]
|
||||
|
||||
const result = consolidateTokenUsage(messages)
|
||||
|
||||
// Context tokens = tokensIn + tokensOut from last message
|
||||
expect(result.contextTokens).toBe(300) // 200 + 100
|
||||
})
|
||||
|
||||
it("should handle condense_context messages for context tokens", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
createApiReqMessage(1000, { tokensIn: 100, tokensOut: 50 }),
|
||||
{
|
||||
ts: 1001,
|
||||
type: "say",
|
||||
say: "condense_context",
|
||||
contextCondense: { newContextTokens: 5000, cost: 0.05 },
|
||||
} as ClineMessage,
|
||||
]
|
||||
|
||||
const result = consolidateTokenUsage(messages)
|
||||
|
||||
expect(result.contextTokens).toBe(5000)
|
||||
expect(result.totalCost).toBeCloseTo(0.05)
|
||||
})
|
||||
})
|
||||
|
||||
describe("invalid data handling", () => {
|
||||
it("should handle messages with invalid JSON", () => {
|
||||
const messages: ClineMessage[] = [{ ts: 1000, type: "say", say: "api_req_started", text: "invalid json" }]
|
||||
|
||||
// Should not throw
|
||||
const result = consolidateTokenUsage(messages)
|
||||
expect(result.totalTokensIn).toBe(0)
|
||||
})
|
||||
|
||||
it("should skip non-api_req_started messages", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
{ ts: 1000, type: "say", say: "text", text: "hello" },
|
||||
createApiReqMessage(1001, { tokensIn: 100, tokensOut: 50 }),
|
||||
]
|
||||
|
||||
const result = consolidateTokenUsage(messages)
|
||||
|
||||
expect(result.totalTokensIn).toBe(100)
|
||||
expect(result.totalTokensOut).toBe(50)
|
||||
})
|
||||
|
||||
it("should handle missing token values", () => {
|
||||
const messages: ClineMessage[] = [createApiReqMessage(1000, { cost: 0.01 })]
|
||||
|
||||
const result = consolidateTokenUsage(messages)
|
||||
|
||||
expect(result.totalTokensIn).toBe(0)
|
||||
expect(result.totalTokensOut).toBe(0)
|
||||
expect(result.totalCost).toBe(0.01)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("hasTokenUsageChanged", () => {
|
||||
it("should return true when snapshot is undefined", () => {
|
||||
const current = {
|
||||
totalTokensIn: 100,
|
||||
totalTokensOut: 50,
|
||||
totalCost: 0.01,
|
||||
contextTokens: 150,
|
||||
}
|
||||
|
||||
expect(hasTokenUsageChanged(current, undefined)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false when values are the same", () => {
|
||||
const current = {
|
||||
totalTokensIn: 100,
|
||||
totalTokensOut: 50,
|
||||
totalCost: 0.01,
|
||||
contextTokens: 150,
|
||||
}
|
||||
const snapshot = { ...current }
|
||||
|
||||
expect(hasTokenUsageChanged(current, snapshot)).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true when totalTokensIn changes", () => {
|
||||
const current = {
|
||||
totalTokensIn: 200,
|
||||
totalTokensOut: 50,
|
||||
totalCost: 0.01,
|
||||
contextTokens: 150,
|
||||
}
|
||||
const snapshot = {
|
||||
totalTokensIn: 100,
|
||||
totalTokensOut: 50,
|
||||
totalCost: 0.01,
|
||||
contextTokens: 150,
|
||||
}
|
||||
|
||||
expect(hasTokenUsageChanged(current, snapshot)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return true when totalCost changes", () => {
|
||||
const current = {
|
||||
totalTokensIn: 100,
|
||||
totalTokensOut: 50,
|
||||
totalCost: 0.02,
|
||||
contextTokens: 150,
|
||||
}
|
||||
const snapshot = {
|
||||
totalTokensIn: 100,
|
||||
totalTokensOut: 50,
|
||||
totalCost: 0.01,
|
||||
contextTokens: 150,
|
||||
}
|
||||
|
||||
expect(hasTokenUsageChanged(current, snapshot)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("hasToolUsageChanged", () => {
|
||||
it("should return true when snapshot is undefined", () => {
|
||||
const current = {
|
||||
read_file: { attempts: 1, failures: 0 },
|
||||
}
|
||||
|
||||
expect(hasToolUsageChanged(current, undefined)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false when values are the same", () => {
|
||||
const current = {
|
||||
read_file: { attempts: 1, failures: 0 },
|
||||
}
|
||||
const snapshot = {
|
||||
read_file: { attempts: 1, failures: 0 },
|
||||
}
|
||||
|
||||
expect(hasToolUsageChanged(current, snapshot)).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true when a tool is added", () => {
|
||||
const current = {
|
||||
read_file: { attempts: 1, failures: 0 },
|
||||
write_to_file: { attempts: 1, failures: 0 },
|
||||
}
|
||||
const snapshot = {
|
||||
read_file: { attempts: 1, failures: 0 },
|
||||
}
|
||||
|
||||
expect(hasToolUsageChanged(current, snapshot)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return true when attempts change", () => {
|
||||
const current = {
|
||||
read_file: { attempts: 2, failures: 0 },
|
||||
}
|
||||
const snapshot = {
|
||||
read_file: { attempts: 1, failures: 0 },
|
||||
}
|
||||
|
||||
expect(hasToolUsageChanged(current, snapshot)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return true when failures change", () => {
|
||||
const current = {
|
||||
read_file: { attempts: 1, failures: 1 },
|
||||
}
|
||||
const snapshot = {
|
||||
read_file: { attempts: 1, failures: 0 },
|
||||
}
|
||||
|
||||
expect(hasToolUsageChanged(current, snapshot)).toBe(true)
|
||||
})
|
||||
})
|
||||
90
packages/core/src/message-utils/consolidateApiRequests.ts
Normal file
90
packages/core/src/message-utils/consolidateApiRequests.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
/**
|
||||
* Consolidates API request start and finish messages in an array of ClineMessages.
|
||||
*
|
||||
* This function looks for pairs of 'api_req_started' and 'api_req_finished' messages.
|
||||
* When it finds a pair, it consolidates them into a single message.
|
||||
* The JSON data in the text fields of both messages are merged.
|
||||
*
|
||||
* @param messages - An array of ClineMessage objects to process.
|
||||
* @returns A new array of ClineMessage objects with API requests consolidated.
|
||||
*
|
||||
* @example
|
||||
* const messages = [
|
||||
* { type: "say", say: "api_req_started", text: '{"request":"GET /api/data"}', ts: 1000 },
|
||||
* { type: "say", say: "api_req_finished", text: '{"cost":0.005}', ts: 1001 }
|
||||
* ];
|
||||
* const result = consolidateApiRequests(messages);
|
||||
* // Result: [{ type: "say", say: "api_req_started", text: '{"request":"GET /api/data","cost":0.005}', ts: 1000 }]
|
||||
*/
|
||||
export function consolidateApiRequests(messages: ClineMessage[]): ClineMessage[] {
|
||||
if (messages.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (messages.length === 1) {
|
||||
return messages
|
||||
}
|
||||
|
||||
let isMergeNecessary = false
|
||||
|
||||
for (const msg of messages) {
|
||||
if (msg.type === "say" && (msg.say === "api_req_started" || msg.say === "api_req_finished")) {
|
||||
isMergeNecessary = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMergeNecessary) {
|
||||
return messages
|
||||
}
|
||||
|
||||
const result: ClineMessage[] = []
|
||||
const startedIndices: number[] = []
|
||||
|
||||
for (const message of messages) {
|
||||
if (message.type !== "say" || (message.say !== "api_req_started" && message.say !== "api_req_finished")) {
|
||||
result.push(message)
|
||||
continue
|
||||
}
|
||||
|
||||
if (message.say === "api_req_started") {
|
||||
// Add to result and track the index.
|
||||
result.push(message)
|
||||
startedIndices.push(result.length - 1)
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the most recent api_req_started that hasn't been consolidated.
|
||||
const startIndex = startedIndices.length > 0 ? startedIndices.pop() : undefined
|
||||
|
||||
if (startIndex !== undefined) {
|
||||
const startMessage = result[startIndex]
|
||||
if (!startMessage) continue
|
||||
|
||||
let startData = {}
|
||||
let finishData = {}
|
||||
|
||||
try {
|
||||
if (startMessage.text) {
|
||||
startData = JSON.parse(startMessage.text)
|
||||
}
|
||||
} catch {
|
||||
// Ignore JSON parse errors
|
||||
}
|
||||
|
||||
try {
|
||||
if (message.text) {
|
||||
finishData = JSON.parse(message.text)
|
||||
}
|
||||
} catch {
|
||||
// Ignore JSON parse errors
|
||||
}
|
||||
|
||||
result[startIndex] = { ...startMessage, text: JSON.stringify({ ...startData, ...finishData }) }
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
160
packages/core/src/message-utils/consolidateCommands.ts
Normal file
160
packages/core/src/message-utils/consolidateCommands.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { safeJsonParse } from "./safeJsonParse.js"
|
||||
|
||||
export const COMMAND_OUTPUT_STRING = "Output:"
|
||||
|
||||
/**
|
||||
* Consolidates sequences of command and command_output messages in an array of ClineMessages.
|
||||
* Also consolidates sequences of use_mcp_server and mcp_server_response messages.
|
||||
*
|
||||
* This function processes an array of ClineMessages objects, looking for sequences
|
||||
* where a 'command' message is followed by one or more 'command_output' messages,
|
||||
* or where a 'use_mcp_server' message is followed by one or more 'mcp_server_response' messages.
|
||||
* When such a sequence is found, it consolidates them into a single message, merging
|
||||
* their text contents.
|
||||
*
|
||||
* @param messages - An array of ClineMessage objects to process.
|
||||
* @returns A new array of ClineMessage objects with command and MCP sequences consolidated.
|
||||
*
|
||||
* @example
|
||||
* const messages: ClineMessage[] = [
|
||||
* { type: 'ask', ask: 'command', text: 'ls', ts: 1625097600000 },
|
||||
* { type: 'ask', ask: 'command_output', text: 'file1.txt', ts: 1625097601000 },
|
||||
* { type: 'ask', ask: 'command_output', text: 'file2.txt', ts: 1625097602000 }
|
||||
* ];
|
||||
* const result = consolidateCommands(messages);
|
||||
* // Result: [{ type: 'ask', ask: 'command', text: 'ls\nfile1.txt\nfile2.txt', ts: 1625097600000 }]
|
||||
*/
|
||||
export function consolidateCommands(messages: ClineMessage[]): ClineMessage[] {
|
||||
const consolidatedMessages = new Map<number, ClineMessage>()
|
||||
const processedIndices = new Set<number>()
|
||||
|
||||
// Single pass through all messages
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i]
|
||||
if (!msg) continue
|
||||
|
||||
// Handle MCP server requests
|
||||
if (msg.type === "ask" && msg.ask === "use_mcp_server") {
|
||||
// Look ahead for MCP responses
|
||||
const responses: string[] = []
|
||||
let j = i + 1
|
||||
|
||||
while (j < messages.length) {
|
||||
const nextMsg = messages[j]
|
||||
if (!nextMsg) {
|
||||
j++
|
||||
continue
|
||||
}
|
||||
if (nextMsg.say === "mcp_server_response") {
|
||||
responses.push(nextMsg.text || "")
|
||||
processedIndices.add(j)
|
||||
j++
|
||||
} else if (nextMsg.type === "ask" && nextMsg.ask === "use_mcp_server") {
|
||||
// Stop if we encounter another MCP request
|
||||
break
|
||||
} else {
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
if (responses.length > 0) {
|
||||
// Parse the JSON from the message text
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const jsonObj = safeJsonParse<any>(msg.text || "{}", {})
|
||||
|
||||
// Add the response to the JSON object
|
||||
jsonObj.response = responses.join("\n")
|
||||
|
||||
// Stringify the updated JSON object
|
||||
const consolidatedText = JSON.stringify(jsonObj)
|
||||
|
||||
consolidatedMessages.set(msg.ts, { ...msg, text: consolidatedText })
|
||||
} else {
|
||||
// If there's no response, just keep the original message
|
||||
consolidatedMessages.set(msg.ts, { ...msg })
|
||||
}
|
||||
}
|
||||
// Handle command sequences
|
||||
else if (msg.type === "ask" && msg.ask === "command") {
|
||||
let consolidatedText = msg.text || ""
|
||||
let j = i + 1
|
||||
let previous: { type: "ask" | "say"; text: string } | undefined
|
||||
let lastProcessedIndex = i
|
||||
|
||||
while (j < messages.length) {
|
||||
const currentMsg = messages[j]
|
||||
if (!currentMsg) {
|
||||
j++
|
||||
continue
|
||||
}
|
||||
const { type, ask, say, text = "" } = currentMsg
|
||||
|
||||
if (type === "ask" && ask === "command") {
|
||||
break // Stop if we encounter the next command.
|
||||
}
|
||||
|
||||
if (ask === "command_output" || say === "command_output") {
|
||||
if (!previous) {
|
||||
consolidatedText += `\n${COMMAND_OUTPUT_STRING}`
|
||||
}
|
||||
|
||||
const isDuplicate = previous && previous.type !== type && previous.text === text
|
||||
|
||||
if (text.length > 0 && !isDuplicate) {
|
||||
// Add a newline before adding the text if there's already content
|
||||
if (
|
||||
previous &&
|
||||
consolidatedText.length >
|
||||
consolidatedText.indexOf(COMMAND_OUTPUT_STRING) + COMMAND_OUTPUT_STRING.length
|
||||
) {
|
||||
consolidatedText += "\n"
|
||||
}
|
||||
consolidatedText += text
|
||||
}
|
||||
|
||||
previous = { type, text }
|
||||
processedIndices.add(j)
|
||||
lastProcessedIndex = j
|
||||
}
|
||||
|
||||
j++
|
||||
}
|
||||
|
||||
consolidatedMessages.set(msg.ts, { ...msg, text: consolidatedText })
|
||||
|
||||
// Only skip ahead if we actually processed command outputs
|
||||
if (lastProcessedIndex > i) {
|
||||
i = lastProcessedIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build final result: filter out processed messages and use consolidated versions
|
||||
const result: ClineMessage[] = []
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i]
|
||||
if (!msg) continue
|
||||
|
||||
// Skip messages that were processed as outputs/responses
|
||||
if (processedIndices.has(i)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip command_output and mcp_server_response messages
|
||||
if (msg.ask === "command_output" || msg.say === "command_output" || msg.say === "mcp_server_response") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use consolidated version if available
|
||||
const consolidatedMsg = consolidatedMessages.get(msg.ts)
|
||||
if (consolidatedMsg) {
|
||||
result.push(consolidatedMsg)
|
||||
} else {
|
||||
result.push(msg)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
157
packages/core/src/message-utils/consolidateTokenUsage.ts
Normal file
157
packages/core/src/message-utils/consolidateTokenUsage.ts
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import type { TokenUsage, ToolUsage, ToolName, ClineMessage } from "@roo-code/types"
|
||||
|
||||
export type ParsedApiReqStartedTextType = {
|
||||
tokensIn: number
|
||||
tokensOut: number
|
||||
cacheWrites: number
|
||||
cacheReads: number
|
||||
cost?: number // Only present if consolidateApiRequests has been called
|
||||
apiProtocol?: "anthropic" | "openai"
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidates token usage metrics from an array of ClineMessages.
|
||||
*
|
||||
* This function processes 'condense_context' messages and 'api_req_started' messages that have been
|
||||
* consolidated with their corresponding 'api_req_finished' messages by the consolidateApiRequests function.
|
||||
* It extracts and sums up the tokensIn, tokensOut, cacheWrites, cacheReads, and cost from these messages.
|
||||
*
|
||||
* @param messages - An array of ClineMessage objects to process.
|
||||
* @returns A TokenUsage object containing totalTokensIn, totalTokensOut, totalCacheWrites, totalCacheReads, totalCost, and contextTokens.
|
||||
*
|
||||
* @example
|
||||
* const messages = [
|
||||
* { type: "say", say: "api_req_started", text: '{"request":"GET /api/data","tokensIn":10,"tokensOut":20,"cost":0.005}', ts: 1000 }
|
||||
* ];
|
||||
* const { totalTokensIn, totalTokensOut, totalCost } = consolidateTokenUsage(messages);
|
||||
* // Result: { totalTokensIn: 10, totalTokensOut: 20, totalCost: 0.005 }
|
||||
*/
|
||||
export function consolidateTokenUsage(messages: ClineMessage[]): TokenUsage {
|
||||
const result: TokenUsage = {
|
||||
totalTokensIn: 0,
|
||||
totalTokensOut: 0,
|
||||
totalCacheWrites: undefined,
|
||||
totalCacheReads: undefined,
|
||||
totalCost: 0,
|
||||
contextTokens: 0,
|
||||
}
|
||||
|
||||
// Calculate running totals.
|
||||
messages.forEach((message) => {
|
||||
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
||||
try {
|
||||
const parsedText: ParsedApiReqStartedTextType = JSON.parse(message.text)
|
||||
const { tokensIn, tokensOut, cacheWrites, cacheReads, cost } = parsedText
|
||||
|
||||
if (typeof tokensIn === "number") {
|
||||
result.totalTokensIn += tokensIn
|
||||
}
|
||||
|
||||
if (typeof tokensOut === "number") {
|
||||
result.totalTokensOut += tokensOut
|
||||
}
|
||||
|
||||
if (typeof cacheWrites === "number") {
|
||||
result.totalCacheWrites = (result.totalCacheWrites ?? 0) + cacheWrites
|
||||
}
|
||||
|
||||
if (typeof cacheReads === "number") {
|
||||
result.totalCacheReads = (result.totalCacheReads ?? 0) + cacheReads
|
||||
}
|
||||
|
||||
if (typeof cost === "number") {
|
||||
result.totalCost += cost
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing JSON:", error)
|
||||
}
|
||||
} else if (message.type === "say" && message.say === "condense_context") {
|
||||
result.totalCost += message.contextCondense?.cost ?? 0
|
||||
}
|
||||
})
|
||||
|
||||
// Calculate context tokens, from the last API request started or condense
|
||||
// context message.
|
||||
result.contextTokens = 0
|
||||
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const message = messages[i]
|
||||
if (!message) continue
|
||||
|
||||
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
||||
try {
|
||||
const parsedText: ParsedApiReqStartedTextType = JSON.parse(message.text)
|
||||
const { tokensIn, tokensOut } = parsedText
|
||||
|
||||
// Since tokensIn now stores TOTAL input tokens (including cache tokens),
|
||||
// we no longer need to add cacheWrites and cacheReads separately.
|
||||
// This applies to both Anthropic and OpenAI protocols.
|
||||
result.contextTokens = (tokensIn || 0) + (tokensOut || 0)
|
||||
} catch {
|
||||
// Ignore JSON parse errors
|
||||
continue
|
||||
}
|
||||
} else if (message.type === "say" && message.say === "condense_context") {
|
||||
result.contextTokens = message.contextCondense?.newContextTokens ?? 0
|
||||
}
|
||||
if (result.contextTokens) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token usage has changed by comparing relevant properties.
|
||||
* @param current - Current token usage data
|
||||
* @param snapshot - Previous snapshot to compare against
|
||||
* @returns true if any relevant property has changed or snapshot is undefined
|
||||
*/
|
||||
export function hasTokenUsageChanged(current: TokenUsage, snapshot?: TokenUsage): boolean {
|
||||
if (!snapshot) {
|
||||
return true
|
||||
}
|
||||
|
||||
const keysToCompare: (keyof TokenUsage)[] = [
|
||||
"totalTokensIn",
|
||||
"totalTokensOut",
|
||||
"totalCacheWrites",
|
||||
"totalCacheReads",
|
||||
"totalCost",
|
||||
"contextTokens",
|
||||
]
|
||||
|
||||
return keysToCompare.some((key) => current[key] !== snapshot[key])
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if tool usage has changed by comparing attempts and failures.
|
||||
* @param current - Current tool usage data
|
||||
* @param snapshot - Previous snapshot to compare against (undefined treated as empty)
|
||||
* @returns true if any tool's attempts/failures have changed between current and snapshot
|
||||
*/
|
||||
export function hasToolUsageChanged(current: ToolUsage, snapshot?: ToolUsage): boolean {
|
||||
// Treat undefined snapshot as empty object for consistent comparison
|
||||
const effectiveSnapshot = snapshot ?? {}
|
||||
|
||||
const currentKeys = Object.keys(current) as ToolName[]
|
||||
const snapshotKeys = Object.keys(effectiveSnapshot) as ToolName[]
|
||||
|
||||
// Check if number of tools changed
|
||||
if (currentKeys.length !== snapshotKeys.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if any tool's stats changed
|
||||
return currentKeys.some((key) => {
|
||||
const currentTool = current[key]
|
||||
const snapshotTool = effectiveSnapshot[key]
|
||||
|
||||
if (!snapshotTool || !currentTool) {
|
||||
return true
|
||||
}
|
||||
|
||||
return currentTool.attempts !== snapshotTool.attempts || currentTool.failures !== snapshotTool.failures
|
||||
})
|
||||
}
|
||||
12
packages/core/src/message-utils/index.ts
Normal file
12
packages/core/src/message-utils/index.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export {
|
||||
type ParsedApiReqStartedTextType,
|
||||
consolidateTokenUsage,
|
||||
hasTokenUsageChanged,
|
||||
hasToolUsageChanged,
|
||||
} from "./consolidateTokenUsage.js"
|
||||
|
||||
export { consolidateApiRequests } from "./consolidateApiRequests.js"
|
||||
|
||||
export { consolidateCommands, COMMAND_OUTPUT_STRING } from "./consolidateCommands.js"
|
||||
|
||||
export { safeJsonParse } from "./safeJsonParse.js"
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Safely parses JSON without crashing on invalid input
|
||||
* Safely parses JSON without crashing on invalid input.
|
||||
*
|
||||
* @param jsonString The string to parse
|
||||
* @param defaultValue Value to return if parsing fails
|
||||
|
|
@ -13,7 +13,7 @@ export function safeJsonParse<T>(jsonString: string | null | undefined, defaultV
|
|||
try {
|
||||
return JSON.parse(jsonString) as T
|
||||
} catch (error) {
|
||||
// Log the error to the console for debugging
|
||||
// Log the error to the console for debugging.
|
||||
console.error("Error parsing JSON:", error)
|
||||
return defaultValue
|
||||
}
|
||||
22
packages/types/src/embedding.ts
Normal file
22
packages/types/src/embedding.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
export type EmbedderProvider =
|
||||
| "openai"
|
||||
| "ollama"
|
||||
| "openai-compatible"
|
||||
| "gemini"
|
||||
| "mistral"
|
||||
| "vercel-ai-gateway"
|
||||
| "bedrock"
|
||||
| "openrouter" // Add other providers as needed.
|
||||
|
||||
export interface EmbeddingModelProfile {
|
||||
dimension: number
|
||||
scoreThreshold?: number // Model-specific minimum score threshold for semantic search.
|
||||
queryPrefix?: string // Optional prefix required by the model for queries.
|
||||
// Add other model-specific properties if needed, e.g., context window size.
|
||||
}
|
||||
|
||||
export type EmbeddingModelProfiles = {
|
||||
[provider in EmbedderProvider]?: {
|
||||
[modelId: string]: EmbeddingModelProfile
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ export * from "./codebase-index.js"
|
|||
export * from "./context-management.js"
|
||||
export * from "./cookie-consent.js"
|
||||
export * from "./custom-tool.js"
|
||||
export * from "./embedding.js"
|
||||
export * from "./events.js"
|
||||
export * from "./experiment.js"
|
||||
export * from "./followup.js"
|
||||
|
|
|
|||
|
|
@ -507,7 +507,6 @@ export interface WebviewMessage {
|
|||
| "requestClaudeCodeRateLimits"
|
||||
| "refreshCustomTools"
|
||||
| "requestModes"
|
||||
| "switchMode"
|
||||
text?: string
|
||||
editedMessageContent?: string
|
||||
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
|
||||
|
|
@ -642,3 +641,140 @@ export type WebViewMessagePayload =
|
|||
| InstallMarketplaceItemWithParametersPayload
|
||||
| UpdateTodoListPayload
|
||||
| EditQueuedMessagePayload
|
||||
|
||||
export interface IndexingStatus {
|
||||
systemStatus: string
|
||||
message?: string
|
||||
processedItems: number
|
||||
totalItems: number
|
||||
currentItemUnit?: string
|
||||
workspacePath?: string
|
||||
}
|
||||
|
||||
export interface IndexingStatusUpdateMessage {
|
||||
type: "indexingStatusUpdate"
|
||||
values: IndexingStatus
|
||||
}
|
||||
|
||||
export interface LanguageModelChatSelector {
|
||||
vendor?: string
|
||||
family?: string
|
||||
version?: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
export interface ClineSayTool {
|
||||
tool:
|
||||
| "editedExistingFile"
|
||||
| "appliedDiff"
|
||||
| "newFileCreated"
|
||||
| "codebaseSearch"
|
||||
| "readFile"
|
||||
| "fetchInstructions"
|
||||
| "listFilesTopLevel"
|
||||
| "listFilesRecursive"
|
||||
| "searchFiles"
|
||||
| "switchMode"
|
||||
| "newTask"
|
||||
| "finishTask"
|
||||
| "generateImage"
|
||||
| "imageGenerated"
|
||||
| "runSlashCommand"
|
||||
| "updateTodoList"
|
||||
path?: string
|
||||
diff?: string
|
||||
content?: string
|
||||
// Unified diff statistics computed by the extension
|
||||
diffStats?: { added: number; removed: number }
|
||||
regex?: string
|
||||
filePattern?: string
|
||||
mode?: string
|
||||
reason?: string
|
||||
isOutsideWorkspace?: boolean
|
||||
isProtected?: boolean
|
||||
additionalFileCount?: number // Number of additional files in the same read_file request
|
||||
lineNumber?: number
|
||||
query?: string
|
||||
batchFiles?: Array<{
|
||||
path: string
|
||||
lineSnippet: string
|
||||
isOutsideWorkspace?: boolean
|
||||
key: string
|
||||
content?: string
|
||||
}>
|
||||
batchDiffs?: Array<{
|
||||
path: string
|
||||
changeCount: number
|
||||
key: string
|
||||
content: string
|
||||
// Per-file unified diff statistics computed by the extension
|
||||
diffStats?: { added: number; removed: number }
|
||||
diffs?: Array<{
|
||||
content: string
|
||||
startLine?: number
|
||||
}>
|
||||
}>
|
||||
question?: string
|
||||
imageData?: string // Base64 encoded image data for generated images
|
||||
// Properties for runSlashCommand tool
|
||||
command?: string
|
||||
args?: string
|
||||
source?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
// Must keep in sync with system prompt.
|
||||
export const browserActions = [
|
||||
"launch",
|
||||
"click",
|
||||
"hover",
|
||||
"type",
|
||||
"press",
|
||||
"scroll_down",
|
||||
"scroll_up",
|
||||
"resize",
|
||||
"close",
|
||||
"screenshot",
|
||||
] as const
|
||||
|
||||
export type BrowserAction = (typeof browserActions)[number]
|
||||
|
||||
export interface ClineSayBrowserAction {
|
||||
action: BrowserAction
|
||||
coordinate?: string
|
||||
size?: string
|
||||
text?: string
|
||||
executedCoordinate?: string
|
||||
}
|
||||
|
||||
export type BrowserActionResult = {
|
||||
screenshot?: string
|
||||
logs?: string
|
||||
currentUrl?: string
|
||||
currentMousePosition?: string
|
||||
viewportWidth?: number
|
||||
viewportHeight?: number
|
||||
}
|
||||
|
||||
export interface ClineAskUseMcpServer {
|
||||
serverName: string
|
||||
type: "use_mcp_tool" | "access_mcp_resource"
|
||||
toolName?: string
|
||||
arguments?: string
|
||||
uri?: string
|
||||
response?: string
|
||||
}
|
||||
|
||||
export interface ClineApiReqInfo {
|
||||
request?: string
|
||||
tokensIn?: number
|
||||
tokensOut?: number
|
||||
cacheWrites?: number
|
||||
cacheReads?: number
|
||||
cost?: number
|
||||
cancelReason?: ClineApiReqCancelReason
|
||||
streamingFailedMessage?: string
|
||||
apiProtocol?: "anthropic" | "openai"
|
||||
}
|
||||
|
||||
export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled"
|
||||
|
|
|
|||
|
|
@ -68,6 +68,13 @@ export interface VSCodeAPIMockOptions {
|
|||
* Defaults to the directory containing this module.
|
||||
*/
|
||||
appRoot?: string
|
||||
|
||||
/**
|
||||
* Custom storage directory for persistent state.
|
||||
* Defaults to ~/.vscode-mock.
|
||||
* Set to a temp directory for ephemeral/no-persist mode.
|
||||
*/
|
||||
storageDir?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -82,6 +89,7 @@ export function createVSCodeAPIMock(
|
|||
const context = new ExtensionContextImpl({
|
||||
extensionPath: extensionRootPath,
|
||||
workspacePath: workspacePath,
|
||||
storageDir: options?.storageDir,
|
||||
})
|
||||
const workspace = new WorkspaceAPI(workspacePath, context)
|
||||
const window = new WindowAPI()
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import {
|
|||
TOOL_PROTOCOL,
|
||||
VERTEX_1M_CONTEXT_MODEL_IDS,
|
||||
} from "@roo-code/types"
|
||||
import { safeJsonParse } from "@roo-code/core"
|
||||
|
||||
import { ApiHandlerOptions } from "../../shared/api"
|
||||
import { safeJsonParse } from "../../shared/safeJsonParse"
|
||||
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { addCacheBreakpoints } from "../transform/caching/vertex"
|
||||
|
|
|
|||
|
|
@ -16,16 +16,15 @@ import {
|
|||
geminiModels,
|
||||
ApiProviderError,
|
||||
} from "@roo-code/types"
|
||||
import { safeJsonParse } from "@roo-code/core"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { safeJsonParse } from "../../shared/safeJsonParse"
|
||||
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
import { t } from "i18next"
|
||||
import type { ApiStream, GroundingSource } from "../transform/stream"
|
||||
import { getModelParams } from "../transform/model-params"
|
||||
import { handleProviderError } from "./utils/error-handler"
|
||||
|
||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { BaseProvider } from "./base-provider"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import {
|
||||
type ClineAsk,
|
||||
type ClineSayTool,
|
||||
type McpServerUse,
|
||||
type FollowUpData,
|
||||
type ExtensionState,
|
||||
isNonBlockingAsk,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import type { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { ClineAskResponse } from "../../shared/WebviewMessage"
|
||||
|
||||
import { isWriteToolAction, isReadOnlyToolAction } from "./tools"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import type { ClineSayTool } from "@roo-code/types"
|
||||
|
||||
export function isWriteToolAction(tool: ClineSayTool): boolean {
|
||||
return ["editedExistingFile", "appliedDiff", "newFileCreated", "generateImage"].includes(tool.tool)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import pWaitFor from "p-wait-for"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import type { ClineApiReqInfo } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
|
|
@ -9,7 +10,6 @@ import { getWorkspacePath } from "../../utils/path"
|
|||
import { checkGitInstalled } from "../../utils/git"
|
||||
import { t } from "../../i18n"
|
||||
|
||||
import { ClineApiReqInfo } from "../../shared/ExtensionMessage"
|
||||
import { getApiMetrics } from "../../shared/getApiMetrics"
|
||||
|
||||
import { DIFF_VIEW_URI_SCHEME } from "../../integrations/editor/DiffViewProvider"
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import {
|
|||
type CreateTaskOptions,
|
||||
type ModelInfo,
|
||||
type ToolProtocol,
|
||||
type ClineApiReqCancelReason,
|
||||
type ClineApiReqInfo,
|
||||
RooCodeEventName,
|
||||
TelemetryEventName,
|
||||
TaskStatus,
|
||||
|
|
@ -65,7 +67,6 @@ import { findLastIndex } from "../../shared/array"
|
|||
import { combineApiRequests } from "../../shared/combineApiRequests"
|
||||
import { combineCommandSequences } from "../../shared/combineCommandSequences"
|
||||
import { t } from "../../i18n"
|
||||
import { ClineApiReqCancelReason, ClineApiReqInfo } from "../../shared/ExtensionMessage"
|
||||
import { getApiMetrics, hasTokenUsageChanged, hasToolUsageChanged } from "../../shared/getApiMetrics"
|
||||
import { ClineAskResponse } from "../../shared/WebviewMessage"
|
||||
import { defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
|
||||
import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
|
|
@ -13,9 +12,10 @@ import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
|
|||
import { unescapeHtmlEntities } from "../../utils/text-normalization"
|
||||
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"
|
||||
import { computeDiffStats, sanitizeUnifiedDiff } from "../diff/stats"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface ApplyDiffParams {
|
||||
path: string
|
||||
diff: string
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
|
||||
import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
|
||||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"
|
||||
import { sanitizeUnifiedDiff, computeDiffStats } from "../diff/stats"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { parseXml } from "../../utils/xml"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface Suggestion {
|
||||
text: string
|
||||
mode?: string
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ import { TelemetryService } from "@roo-code/telemetry"
|
|||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { Package } from "../../shared/package"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
import { t } from "../../i18n"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface AttemptCompletionParams {
|
||||
result: string
|
||||
command?: string
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import type { ToolName, ToolProtocol } from "@roo-code/types"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import type {
|
||||
ToolUse,
|
||||
|
|
@ -7,7 +9,6 @@ import type {
|
|||
AskApproval,
|
||||
NativeToolArgs,
|
||||
} from "../../shared/tools"
|
||||
import type { ToolName, ToolProtocol } from "@roo-code/types"
|
||||
|
||||
/**
|
||||
* Callbacks passed to tool execution
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
import { BrowserAction, BrowserActionResult, browserActions, ClineSayBrowserAction } from "@roo-code/types"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
|
||||
import {
|
||||
BrowserAction,
|
||||
BrowserActionResult,
|
||||
browserActions,
|
||||
ClineSayBrowserAction,
|
||||
} from "../../shared/ExtensionMessage"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
import { scaleCoordinate } from "../../shared/browserUtils"
|
||||
|
||||
export async function browserActionTool(
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import { CodeIndexManager } from "../../services/code-index/manager"
|
|||
import { getWorkspacePath } from "../../utils/path"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { VectorStoreSearchResult } from "../../services/code-index/interfaces"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface CodebaseSearchParams {
|
||||
query: string
|
||||
path?: string
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
|
||||
import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
|
||||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"
|
||||
import { sanitizeUnifiedDiff, computeDiffStats } from "../diff/stats"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface EditFileParams {
|
||||
file_path: string
|
||||
old_string: string
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { type ClineSayTool } from "@roo-code/types"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import { fetchInstructions } from "../prompts/instructions/instructions"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface FetchInstructionsParams {
|
||||
task: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import * as path from "path"
|
||||
|
||||
import { type ClineSayTool } from "@roo-code/types"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { listFiles } from "../../services/glob/list-files"
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface ListFilesParams {
|
||||
path: string
|
||||
recursive?: boolean
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
|
||||
import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS, isNativeProtocol } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { Task } from "../task/Task"
|
||||
import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools"
|
||||
|
|
@ -16,7 +15,6 @@ import { parseXmlForDiff } from "../../utils/xml"
|
|||
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"
|
||||
import { applyDiffTool as applyDiffToolClass } from "./ApplyDiffTool"
|
||||
import { computeDiffStats, sanitizeUnifiedDiff } from "../diff/stats"
|
||||
import { isNativeProtocol } from "@roo-code/types"
|
||||
import { resolveToolProtocol } from "../../utils/resolveToolProtocol"
|
||||
|
||||
interface DiffOperation {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import path from "path"
|
||||
import * as fs from "fs/promises"
|
||||
import { isBinaryFile } from "isbinaryfile"
|
||||
|
||||
import type { FileEntry, LineRange } from "@roo-code/types"
|
||||
import { isNativeProtocol, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types"
|
||||
import { type ClineSayTool, isNativeProtocol, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { getModelMaxOutputTokens } from "../../shared/api"
|
||||
import { t } from "../../i18n"
|
||||
|
|
@ -18,6 +18,8 @@ import { extractTextFromFile, addLineNumbers, getSupportedBinaryFormats } from "
|
|||
import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter"
|
||||
import { parseXml } from "../../utils/xml"
|
||||
import { resolveToolProtocol } from "../../utils/resolveToolProtocol"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import {
|
||||
DEFAULT_MAX_IMAGE_FILE_SIZE_MB,
|
||||
DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB,
|
||||
|
|
@ -29,7 +31,6 @@ import {
|
|||
import { FILE_READ_BUDGET_PERCENT, readFileWithTokenBudget } from "./helpers/fileTokenBudget"
|
||||
import { truncateDefinitionsToLineLimit } from "./helpers/truncateDefinitions"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
interface FileResult {
|
||||
path: string
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
|
||||
import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
|
||||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"
|
||||
import { sanitizeUnifiedDiff, computeDiffStats } from "../diff/stats"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface SearchReplaceOperation {
|
||||
search: string
|
||||
replace: string
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import path from "path"
|
||||
|
||||
import { type ClineSayTool } from "@roo-code/types"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
|
||||
import { regexSearchFiles } from "../../services/ripgrep"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface SearchFilesParams {
|
||||
path: string
|
||||
regex: string
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
|
||||
import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
|
||||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"
|
||||
import { sanitizeUnifiedDiff, computeDiffStats } from "../diff/stats"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface SearchReplaceParams {
|
||||
file_path: string
|
||||
old_string: string
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import type { ClineAskUseMcpServer, McpExecutionStatus } from "@roo-code/types"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage"
|
||||
import { McpExecutionStatus } from "@roo-code/types"
|
||||
import { t } from "../../i18n"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface UseMcpToolParams {
|
||||
server_name: string
|
||||
tool_name: string
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import path from "path"
|
||||
import delay from "delay"
|
||||
import * as vscode from "vscode"
|
||||
import fs from "fs/promises"
|
||||
|
||||
import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
|
||||
import { Task } from "../task/Task"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
|
||||
import { fileExistsAtPath, createDirectoriesForFile } from "../../utils/fs"
|
||||
|
|
@ -12,12 +12,12 @@ import { stripLineNumbers, everyLineHasLineNumbers } from "../../integrations/mi
|
|||
import { getReadablePath } from "../../utils/path"
|
||||
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
|
||||
import { unescapeHtmlEntities } from "../../utils/text-normalization"
|
||||
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
|
||||
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"
|
||||
import { convertNewFileToUnifiedDiff, computeDiffStats, sanitizeUnifiedDiff } from "../diff/stats"
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface WriteToFileParams {
|
||||
path: string
|
||||
content: string
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
// Test screenshot action functionality in browser actions
|
||||
import { describe, it, expect } from "vitest"
|
||||
import { browserActions } from "../../../shared/ExtensionMessage"
|
||||
import { browserActions } from "@roo-code/types"
|
||||
|
||||
describe("Browser Action Screenshot", () => {
|
||||
describe("browserActions array", () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage"
|
||||
import type { ClineAskUseMcpServer } from "@roo-code/types"
|
||||
|
||||
import type { ToolUse } from "../../shared/tools"
|
||||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
|
||||
import { BaseTool, ToolCallbacks } from "./BaseTool"
|
||||
|
||||
interface AccessMcpResourceParams {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import stripBom from "strip-bom"
|
|||
import { XMLBuilder } from "fast-xml-parser"
|
||||
import delay from "delay"
|
||||
|
||||
import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS, isNativeProtocol } from "@roo-code/types"
|
||||
|
||||
import { createDirectoriesForFile } from "../../utils/fs"
|
||||
import { arePathsEqual, getReadablePath } from "../../utils/path"
|
||||
import { formatResponse } from "../../core/prompts/responses"
|
||||
import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { Task } from "../../core/task/Task"
|
||||
import { DEFAULT_WRITE_DELAY_MS, isNativeProtocol } from "@roo-code/types"
|
||||
import { resolveToolProtocol } from "../../utils/resolveToolProtocol"
|
||||
|
||||
import { DecorationController } from "./DecorationController"
|
||||
|
|
|
|||
|
|
@ -6,8 +6,11 @@ import { Browser, Page, ScreenshotOptions, TimeoutError, launch, connect, KeyInp
|
|||
import PCR from "puppeteer-chromium-resolver"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import delay from "delay"
|
||||
|
||||
import { type BrowserActionResult } from "@roo-code/types"
|
||||
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { BrowserActionResult } from "../../shared/ExtensionMessage"
|
||||
|
||||
import { discoverChromeHostUrl, tryChromeHostUrl } from "./browserDiscovery"
|
||||
|
||||
// Timeout constants
|
||||
|
|
|
|||
|
|
@ -1,4 +1,17 @@
|
|||
import * as vscode from "vscode"
|
||||
import { Ignore } from "ignore"
|
||||
|
||||
import type { EmbedderProvider } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { TelemetryEventName } from "@roo-code/types"
|
||||
|
||||
import { t } from "../../i18n"
|
||||
|
||||
import { getDefaultModelId, getModelDimension } from "../../shared/embeddingModels"
|
||||
import { Package } from "../../shared/package"
|
||||
|
||||
import { RooIgnoreController } from "../../core/ignore/RooIgnoreController"
|
||||
|
||||
import { OpenAiEmbedder } from "./embedders/openai"
|
||||
import { CodeIndexOllamaEmbedder } from "./embedders/ollama"
|
||||
import { OpenAICompatibleEmbedder } from "./embedders/openai-compatible"
|
||||
|
|
@ -7,18 +20,11 @@ import { MistralEmbedder } from "./embedders/mistral"
|
|||
import { VercelAiGatewayEmbedder } from "./embedders/vercel-ai-gateway"
|
||||
import { BedrockEmbedder } from "./embedders/bedrock"
|
||||
import { OpenRouterEmbedder } from "./embedders/openrouter"
|
||||
import { EmbedderProvider, getDefaultModelId, getModelDimension } from "../../shared/embeddingModels"
|
||||
import { QdrantVectorStore } from "./vector-store/qdrant-client"
|
||||
import { codeParser, DirectoryScanner, FileWatcher } from "./processors"
|
||||
import { ICodeParser, IEmbedder, IFileWatcher, IVectorStore } from "./interfaces"
|
||||
import { CodeIndexConfigManager } from "./config-manager"
|
||||
import { CacheManager } from "./cache-manager"
|
||||
import { RooIgnoreController } from "../../core/ignore/RooIgnoreController"
|
||||
import { Ignore } from "ignore"
|
||||
import { t } from "../../i18n"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { TelemetryEventName } from "@roo-code/types"
|
||||
import { Package } from "../../shared/package"
|
||||
import { BATCH_SEGMENT_THRESHOLD } from "./constants"
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
export interface IndexingStatus {
|
||||
systemStatus: string
|
||||
message?: string
|
||||
processedItems: number
|
||||
totalItems: number
|
||||
currentItemUnit?: string
|
||||
workspacePath?: string
|
||||
}
|
||||
|
||||
export interface IndexingStatusUpdateMessage {
|
||||
type: "indexingStatusUpdate"
|
||||
values: IndexingStatus
|
||||
}
|
||||
|
||||
export interface LanguageModelChatSelector {
|
||||
vendor?: string
|
||||
family?: string
|
||||
version?: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
export interface ClineSayTool {
|
||||
tool:
|
||||
| "editedExistingFile"
|
||||
| "appliedDiff"
|
||||
| "newFileCreated"
|
||||
| "codebaseSearch"
|
||||
| "readFile"
|
||||
| "fetchInstructions"
|
||||
| "listFilesTopLevel"
|
||||
| "listFilesRecursive"
|
||||
| "searchFiles"
|
||||
| "switchMode"
|
||||
| "newTask"
|
||||
| "finishTask"
|
||||
| "generateImage"
|
||||
| "imageGenerated"
|
||||
| "runSlashCommand"
|
||||
| "updateTodoList"
|
||||
path?: string
|
||||
diff?: string
|
||||
content?: string
|
||||
// Unified diff statistics computed by the extension
|
||||
diffStats?: { added: number; removed: number }
|
||||
regex?: string
|
||||
filePattern?: string
|
||||
mode?: string
|
||||
reason?: string
|
||||
isOutsideWorkspace?: boolean
|
||||
isProtected?: boolean
|
||||
additionalFileCount?: number // Number of additional files in the same read_file request
|
||||
lineNumber?: number
|
||||
query?: string
|
||||
batchFiles?: Array<{
|
||||
path: string
|
||||
lineSnippet: string
|
||||
isOutsideWorkspace?: boolean
|
||||
key: string
|
||||
content?: string
|
||||
}>
|
||||
batchDiffs?: Array<{
|
||||
path: string
|
||||
changeCount: number
|
||||
key: string
|
||||
content: string
|
||||
// Per-file unified diff statistics computed by the extension
|
||||
diffStats?: { added: number; removed: number }
|
||||
diffs?: Array<{
|
||||
content: string
|
||||
startLine?: number
|
||||
}>
|
||||
}>
|
||||
question?: string
|
||||
imageData?: string // Base64 encoded image data for generated images
|
||||
// Properties for runSlashCommand tool
|
||||
command?: string
|
||||
args?: string
|
||||
source?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
// Must keep in sync with system prompt.
|
||||
export const browserActions = [
|
||||
"launch",
|
||||
"click",
|
||||
"hover",
|
||||
"type",
|
||||
"press",
|
||||
"scroll_down",
|
||||
"scroll_up",
|
||||
"resize",
|
||||
"close",
|
||||
"screenshot",
|
||||
] as const
|
||||
|
||||
export type BrowserAction = (typeof browserActions)[number]
|
||||
|
||||
export interface ClineSayBrowserAction {
|
||||
action: BrowserAction
|
||||
coordinate?: string
|
||||
size?: string
|
||||
text?: string
|
||||
executedCoordinate?: string
|
||||
}
|
||||
|
||||
export type BrowserActionResult = {
|
||||
screenshot?: string
|
||||
logs?: string
|
||||
currentUrl?: string
|
||||
currentMousePosition?: string
|
||||
viewportWidth?: number
|
||||
viewportHeight?: number
|
||||
}
|
||||
|
||||
export interface ClineAskUseMcpServer {
|
||||
serverName: string
|
||||
type: "use_mcp_tool" | "access_mcp_resource"
|
||||
toolName?: string
|
||||
arguments?: string
|
||||
uri?: string
|
||||
response?: string
|
||||
}
|
||||
|
||||
export interface ClineApiReqInfo {
|
||||
request?: string
|
||||
tokensIn?: number
|
||||
tokensOut?: number
|
||||
cacheWrites?: number
|
||||
cacheReads?: number
|
||||
cost?: number
|
||||
cancelReason?: ClineApiReqCancelReason
|
||||
streamingFailedMessage?: string
|
||||
apiProtocol?: "anthropic" | "openai"
|
||||
}
|
||||
|
||||
export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled"
|
||||
|
|
@ -1,84 +1,3 @@
|
|||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { consolidateApiRequests as combineApiRequests } from "@roo-code/core/browser"
|
||||
|
||||
/**
|
||||
* Combines API request start and finish messages in an array of ClineMessages.
|
||||
*
|
||||
* This function looks for pairs of 'api_req_started' and 'api_req_finished' messages.
|
||||
* When it finds a pair, it combines them into a single 'api_req_combined' message.
|
||||
* The JSON data in the text fields of both messages are merged.
|
||||
*
|
||||
* @param messages - An array of ClineMessage objects to process.
|
||||
* @returns A new array of ClineMessage objects with API requests combined.
|
||||
*
|
||||
* @example
|
||||
* const messages = [
|
||||
* { type: "say", say: "api_req_started", text: '{"request":"GET /api/data"}', ts: 1000 },
|
||||
* { type: "say", say: "api_req_finished", text: '{"cost":0.005}', ts: 1001 }
|
||||
* ];
|
||||
* const result = combineApiRequests(messages);
|
||||
* // Result: [{ type: "say", say: "api_req_started", text: '{"request":"GET /api/data","cost":0.005}', ts: 1000 }]
|
||||
*/
|
||||
export function combineApiRequests(messages: ClineMessage[]): ClineMessage[] {
|
||||
if (messages.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (messages.length === 1) {
|
||||
return messages
|
||||
}
|
||||
|
||||
let isMergeNecessary = false
|
||||
|
||||
for (const msg of messages) {
|
||||
if (msg.type === "say" && (msg.say === "api_req_started" || msg.say === "api_req_finished")) {
|
||||
isMergeNecessary = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMergeNecessary) {
|
||||
return messages
|
||||
}
|
||||
|
||||
const result: ClineMessage[] = []
|
||||
const startedIndices: number[] = []
|
||||
|
||||
for (const message of messages) {
|
||||
if (message.type !== "say" || (message.say !== "api_req_started" && message.say !== "api_req_finished")) {
|
||||
result.push(message)
|
||||
continue
|
||||
}
|
||||
|
||||
if (message.say === "api_req_started") {
|
||||
// Add to result and track the index.
|
||||
result.push(message)
|
||||
startedIndices.push(result.length - 1)
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the most recent api_req_started that hasn't been combined.
|
||||
const startIndex = startedIndices.length > 0 ? startedIndices.pop() : undefined
|
||||
|
||||
if (startIndex !== undefined) {
|
||||
const startMessage = result[startIndex]
|
||||
let startData = {}
|
||||
let finishData = {}
|
||||
|
||||
try {
|
||||
if (startMessage.text) {
|
||||
startData = JSON.parse(startMessage.text)
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
if (message.text) {
|
||||
finishData = JSON.parse(message.text)
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
result[startIndex] = { ...startMessage, text: JSON.stringify({ ...startData, ...finishData }) }
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
export { combineApiRequests }
|
||||
|
|
|
|||
|
|
@ -1,146 +1,3 @@
|
|||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { consolidateCommands as combineCommandSequences, COMMAND_OUTPUT_STRING } from "@roo-code/core/browser"
|
||||
|
||||
import { safeJsonParse } from "./safeJsonParse"
|
||||
|
||||
export const COMMAND_OUTPUT_STRING = "Output:"
|
||||
|
||||
/**
|
||||
* Combines sequences of command and command_output messages in an array of ClineMessages.
|
||||
* Also combines sequences of use_mcp_server and mcp_server_response messages.
|
||||
*
|
||||
* This function processes an array of ClineMessages objects, looking for sequences
|
||||
* where a 'command' message is followed by one or more 'command_output' messages,
|
||||
* or where a 'use_mcp_server' message is followed by one or more 'mcp_server_response' messages.
|
||||
* When such a sequence is found, it combines them into a single message, merging
|
||||
* their text contents.
|
||||
*
|
||||
* @param messages - An array of ClineMessage objects to process.
|
||||
* @returns A new array of ClineMessage objects with command and MCP sequences combined.
|
||||
*
|
||||
* @example
|
||||
* const messages: ClineMessage[] = [
|
||||
* { type: 'ask', ask: 'command', text: 'ls', ts: 1625097600000 },
|
||||
* { type: 'ask', ask: 'command_output', text: 'file1.txt', ts: 1625097601000 },
|
||||
* { type: 'ask', ask: 'command_output', text: 'file2.txt', ts: 1625097602000 }
|
||||
* ];
|
||||
* const result = simpleCombineCommandSequences(messages);
|
||||
* // Result: [{ type: 'ask', ask: 'command', text: 'ls\nfile1.txt\nfile2.txt', ts: 1625097600000 }]
|
||||
*/
|
||||
export function combineCommandSequences(messages: ClineMessage[]): ClineMessage[] {
|
||||
const combinedMessages = new Map<number, ClineMessage>()
|
||||
const processedIndices = new Set<number>()
|
||||
|
||||
// Single pass through all messages
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i]
|
||||
|
||||
// Handle MCP server requests
|
||||
if (msg.type === "ask" && msg.ask === "use_mcp_server") {
|
||||
// Look ahead for MCP responses
|
||||
let responses: string[] = []
|
||||
let j = i + 1
|
||||
|
||||
while (j < messages.length) {
|
||||
if (messages[j].say === "mcp_server_response") {
|
||||
responses.push(messages[j].text || "")
|
||||
processedIndices.add(j)
|
||||
j++
|
||||
} else if (messages[j].type === "ask" && messages[j].ask === "use_mcp_server") {
|
||||
// Stop if we encounter another MCP request
|
||||
break
|
||||
} else {
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
if (responses.length > 0) {
|
||||
// Parse the JSON from the message text
|
||||
const jsonObj = safeJsonParse<any>(msg.text || "{}", {})
|
||||
|
||||
// Add the response to the JSON object
|
||||
jsonObj.response = responses.join("\n")
|
||||
|
||||
// Stringify the updated JSON object
|
||||
const combinedText = JSON.stringify(jsonObj)
|
||||
|
||||
combinedMessages.set(msg.ts, { ...msg, text: combinedText })
|
||||
} else {
|
||||
// If there's no response, just keep the original message
|
||||
combinedMessages.set(msg.ts, { ...msg })
|
||||
}
|
||||
}
|
||||
// Handle command sequences
|
||||
else if (msg.type === "ask" && msg.ask === "command") {
|
||||
let combinedText = msg.text || ""
|
||||
let j = i + 1
|
||||
let previous: { type: "ask" | "say"; text: string } | undefined
|
||||
let lastProcessedIndex = i
|
||||
|
||||
while (j < messages.length) {
|
||||
const { type, ask, say, text = "" } = messages[j]
|
||||
|
||||
if (type === "ask" && ask === "command") {
|
||||
break // Stop if we encounter the next command.
|
||||
}
|
||||
|
||||
if (ask === "command_output" || say === "command_output") {
|
||||
if (!previous) {
|
||||
combinedText += `\n${COMMAND_OUTPUT_STRING}`
|
||||
}
|
||||
|
||||
const isDuplicate = previous && previous.type !== type && previous.text === text
|
||||
|
||||
if (text.length > 0 && !isDuplicate) {
|
||||
// Add a newline before adding the text if there's already content
|
||||
if (
|
||||
previous &&
|
||||
combinedText.length >
|
||||
combinedText.indexOf(COMMAND_OUTPUT_STRING) + COMMAND_OUTPUT_STRING.length
|
||||
) {
|
||||
combinedText += "\n"
|
||||
}
|
||||
combinedText += text
|
||||
}
|
||||
|
||||
previous = { type, text }
|
||||
processedIndices.add(j)
|
||||
lastProcessedIndex = j
|
||||
}
|
||||
|
||||
j++
|
||||
}
|
||||
|
||||
combinedMessages.set(msg.ts, { ...msg, text: combinedText })
|
||||
|
||||
// Only skip ahead if we actually processed command outputs
|
||||
if (lastProcessedIndex > i) {
|
||||
i = lastProcessedIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build final result: filter out processed messages and use combined versions
|
||||
const result: ClineMessage[] = []
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i]
|
||||
|
||||
// Skip messages that were processed as outputs/responses
|
||||
if (processedIndices.has(i)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip command_output and mcp_server_response messages
|
||||
if (msg.ask === "command_output" || msg.say === "command_output" || msg.say === "mcp_server_response") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use combined version if available
|
||||
if (combinedMessages.has(msg.ts)) {
|
||||
result.push(combinedMessages.get(msg.ts)!)
|
||||
} else {
|
||||
result.push(msg)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
export { combineCommandSequences, COMMAND_OUTPUT_STRING }
|
||||
|
|
|
|||
1
src/shared/core.ts
Normal file
1
src/shared/core.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "@roo-code/core/browser"
|
||||
|
|
@ -2,28 +2,7 @@
|
|||
* Defines profiles for different embedding models, including their dimensions.
|
||||
*/
|
||||
|
||||
export type EmbedderProvider =
|
||||
| "openai"
|
||||
| "ollama"
|
||||
| "openai-compatible"
|
||||
| "gemini"
|
||||
| "mistral"
|
||||
| "vercel-ai-gateway"
|
||||
| "bedrock"
|
||||
| "openrouter" // Add other providers as needed
|
||||
|
||||
export interface EmbeddingModelProfile {
|
||||
dimension: number
|
||||
scoreThreshold?: number // Model-specific minimum score threshold for semantic search
|
||||
queryPrefix?: string // Optional prefix required by the model for queries
|
||||
// Add other model-specific properties if needed, e.g., context window size
|
||||
}
|
||||
|
||||
export type EmbeddingModelProfiles = {
|
||||
[provider in EmbedderProvider]?: {
|
||||
[modelId: string]: EmbeddingModelProfile
|
||||
}
|
||||
}
|
||||
import type { EmbedderProvider, EmbeddingModelProfiles } from "@roo-code/types"
|
||||
|
||||
// Example profiles - expand this list as needed
|
||||
export const EMBEDDING_MODEL_PROFILES: EmbeddingModelProfiles = {
|
||||
|
|
|
|||
|
|
@ -1,156 +1,8 @@
|
|||
import type { TokenUsage, ToolUsage, ToolName, ClineMessage } from "@roo-code/types"
|
||||
import {
|
||||
type ParsedApiReqStartedTextType,
|
||||
consolidateTokenUsage as getApiMetrics,
|
||||
hasTokenUsageChanged,
|
||||
hasToolUsageChanged,
|
||||
} from "@roo-code/core/browser"
|
||||
|
||||
export type ParsedApiReqStartedTextType = {
|
||||
tokensIn: number
|
||||
tokensOut: number
|
||||
cacheWrites: number
|
||||
cacheReads: number
|
||||
cost?: number // Only present if combineApiRequests has been called
|
||||
apiProtocol?: "anthropic" | "openai"
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates API metrics from an array of ClineMessages.
|
||||
*
|
||||
* This function processes 'condense_context' messages and 'api_req_started' messages that have been
|
||||
* combined with their corresponding 'api_req_finished' messages by the combineApiRequests function.
|
||||
* It extracts and sums up the tokensIn, tokensOut, cacheWrites, cacheReads, and cost from these messages.
|
||||
*
|
||||
* @param messages - An array of ClineMessage objects to process.
|
||||
* @returns An ApiMetrics object containing totalTokensIn, totalTokensOut, totalCacheWrites, totalCacheReads, totalCost, and contextTokens.
|
||||
*
|
||||
* @example
|
||||
* const messages = [
|
||||
* { type: "say", say: "api_req_started", text: '{"request":"GET /api/data","tokensIn":10,"tokensOut":20,"cost":0.005}', ts: 1000 }
|
||||
* ];
|
||||
* const { totalTokensIn, totalTokensOut, totalCost } = getApiMetrics(messages);
|
||||
* // Result: { totalTokensIn: 10, totalTokensOut: 20, totalCost: 0.005 }
|
||||
*/
|
||||
export function getApiMetrics(messages: ClineMessage[]) {
|
||||
const result: TokenUsage = {
|
||||
totalTokensIn: 0,
|
||||
totalTokensOut: 0,
|
||||
totalCacheWrites: undefined,
|
||||
totalCacheReads: undefined,
|
||||
totalCost: 0,
|
||||
contextTokens: 0,
|
||||
}
|
||||
|
||||
// Calculate running totals.
|
||||
messages.forEach((message) => {
|
||||
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
||||
try {
|
||||
const parsedText: ParsedApiReqStartedTextType = JSON.parse(message.text)
|
||||
const { tokensIn, tokensOut, cacheWrites, cacheReads, cost } = parsedText
|
||||
|
||||
if (typeof tokensIn === "number") {
|
||||
result.totalTokensIn += tokensIn
|
||||
}
|
||||
|
||||
if (typeof tokensOut === "number") {
|
||||
result.totalTokensOut += tokensOut
|
||||
}
|
||||
|
||||
if (typeof cacheWrites === "number") {
|
||||
result.totalCacheWrites = (result.totalCacheWrites ?? 0) + cacheWrites
|
||||
}
|
||||
|
||||
if (typeof cacheReads === "number") {
|
||||
result.totalCacheReads = (result.totalCacheReads ?? 0) + cacheReads
|
||||
}
|
||||
|
||||
if (typeof cost === "number") {
|
||||
result.totalCost += cost
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing JSON:", error)
|
||||
}
|
||||
} else if (message.type === "say" && message.say === "condense_context") {
|
||||
result.totalCost += message.contextCondense?.cost ?? 0
|
||||
}
|
||||
})
|
||||
|
||||
// Calculate context tokens, from the last API request started or condense
|
||||
// context message.
|
||||
result.contextTokens = 0
|
||||
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const message = messages[i]
|
||||
|
||||
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
||||
try {
|
||||
const parsedText: ParsedApiReqStartedTextType = JSON.parse(message.text)
|
||||
const { tokensIn, tokensOut } = parsedText
|
||||
|
||||
// Since tokensIn now stores TOTAL input tokens (including cache tokens),
|
||||
// we no longer need to add cacheWrites and cacheReads separately.
|
||||
// This applies to both Anthropic and OpenAI protocols.
|
||||
result.contextTokens = (tokensIn || 0) + (tokensOut || 0)
|
||||
} catch (error) {
|
||||
console.error("Error parsing JSON:", error)
|
||||
continue
|
||||
}
|
||||
} else if (message.type === "say" && message.say === "condense_context") {
|
||||
result.contextTokens = message.contextCondense?.newContextTokens ?? 0
|
||||
}
|
||||
if (result.contextTokens) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token usage has changed by comparing relevant properties.
|
||||
* @param current - Current token usage data
|
||||
* @param snapshot - Previous snapshot to compare against
|
||||
* @returns true if any relevant property has changed or snapshot is undefined
|
||||
*/
|
||||
export function hasTokenUsageChanged(current: TokenUsage, snapshot?: TokenUsage): boolean {
|
||||
if (!snapshot) {
|
||||
return true
|
||||
}
|
||||
|
||||
const keysToCompare: (keyof TokenUsage)[] = [
|
||||
"totalTokensIn",
|
||||
"totalTokensOut",
|
||||
"totalCacheWrites",
|
||||
"totalCacheReads",
|
||||
"totalCost",
|
||||
"contextTokens",
|
||||
]
|
||||
|
||||
return keysToCompare.some((key) => current[key] !== snapshot[key])
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if tool usage has changed by comparing attempts and failures.
|
||||
* @param current - Current tool usage data
|
||||
* @param snapshot - Previous snapshot to compare against (undefined treated as empty)
|
||||
* @returns true if any tool's attempts/failures have changed between current and snapshot
|
||||
*/
|
||||
export function hasToolUsageChanged(current: ToolUsage, snapshot?: ToolUsage): boolean {
|
||||
// Treat undefined snapshot as empty object for consistent comparison
|
||||
const effectiveSnapshot = snapshot ?? {}
|
||||
|
||||
const currentKeys = Object.keys(current) as ToolName[]
|
||||
const snapshotKeys = Object.keys(effectiveSnapshot) as ToolName[]
|
||||
|
||||
// Check if number of tools changed
|
||||
if (currentKeys.length !== snapshotKeys.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if any tool's stats changed
|
||||
return currentKeys.some((key) => {
|
||||
const currentTool = current[key]
|
||||
const snapshotTool = effectiveSnapshot[key]
|
||||
|
||||
if (!snapshotTool || !currentTool) {
|
||||
return true
|
||||
}
|
||||
|
||||
return currentTool.attempts !== snapshotTool.attempts || currentTool.failures !== snapshotTool.failures
|
||||
})
|
||||
}
|
||||
export { type ParsedApiReqStartedTextType, getApiMetrics, hasTokenUsageChanged, hasToolUsageChanged }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ClineMessage } from "@roo-code/types"
|
||||
|
||||
export function getLatestTodo(clineMessages: ClineMessage[]) {
|
||||
const todos = clineMessages
|
||||
.filter(
|
||||
|
|
@ -15,6 +16,7 @@ export function getLatestTodo(clineMessages: ClineMessage[]) {
|
|||
.filter((item) => item && item.tool === "updateTodoList" && Array.isArray(item.todos))
|
||||
.map((item) => item.todos)
|
||||
.pop()
|
||||
|
||||
if (todos) {
|
||||
return todos
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import { memo, useMemo, useEffect, useRef } from "react"
|
||||
import { ClineMessage } from "@roo-code/types"
|
||||
import { ClineSayBrowserAction } from "@roo/ExtensionMessage"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { getViewportCoordinate as getViewportCoordinateShared, prettyKey } from "@roo/browserUtils"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import {
|
||||
MousePointer as MousePointerIcon,
|
||||
Keyboard,
|
||||
|
|
@ -14,8 +11,13 @@ import {
|
|||
Maximize2,
|
||||
Camera,
|
||||
} from "lucide-react"
|
||||
|
||||
import type { ClineMessage, ClineSayBrowserAction } from "@roo-code/types"
|
||||
|
||||
import { getViewportCoordinate as getViewportCoordinateShared, prettyKey } from "@roo/browserUtils"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
interface BrowserActionRowProps {
|
||||
message: ClineMessage
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ import React, { memo, useEffect, useMemo, useRef, useState } from "react"
|
|||
import deepEqual from "fast-deep-equal"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import type { TFunction } from "i18next"
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
|
||||
import { BrowserAction, BrowserActionResult, ClineSayBrowserAction } from "@roo/ExtensionMessage"
|
||||
import type { ClineMessage, BrowserAction, BrowserActionResult, ClineSayBrowserAction } from "@roo-code/types"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
|
|
|
|||
|
|
@ -4,12 +4,19 @@ import { useTranslation, Trans } from "react-i18next"
|
|||
import deepEqual from "fast-deep-equal"
|
||||
import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
import type { ClineMessage, FollowUpData, SuggestionItem } from "@roo-code/types"
|
||||
import type {
|
||||
ClineMessage,
|
||||
FollowUpData,
|
||||
SuggestionItem,
|
||||
ClineApiReqInfo,
|
||||
ClineAskUseMcpServer,
|
||||
ClineSayTool,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { Mode } from "@roo/modes"
|
||||
|
||||
import { ClineApiReqInfo, ClineAskUseMcpServer, ClineSayTool } from "@roo/ExtensionMessage"
|
||||
import { COMMAND_OUTPUT_STRING } from "@roo/combineCommandSequences"
|
||||
import { safeJsonParse } from "@roo/safeJsonParse"
|
||||
import { safeJsonParse } from "@roo/core"
|
||||
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
|
||||
|
|
|
|||
|
|
@ -11,9 +11,8 @@ import { Trans } from "react-i18next"
|
|||
import { useDebounceEffect } from "@src/utils/useDebounceEffect"
|
||||
import { appendImages } from "@src/utils/imageUtils"
|
||||
|
||||
import type { ClineAsk, ClineMessage, ExtensionMessage, AudioType } from "@roo-code/types"
|
||||
import type { ClineAsk, ClineSayTool, ClineMessage, ExtensionMessage, AudioType } from "@roo-code/types"
|
||||
|
||||
import { ClineSayTool } from "@roo/ExtensionMessage"
|
||||
import { findLast } from "@roo/array"
|
||||
import { SuggestionItem } from "@roo-code/types"
|
||||
import { combineApiRequests } from "@roo/combineApiRequests"
|
||||
|
|
|
|||
|
|
@ -12,10 +12,7 @@ import {
|
|||
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||
import { AlertTriangle } from "lucide-react"
|
||||
|
||||
import { CODEBASE_INDEX_DEFAULTS } from "@roo-code/types"
|
||||
|
||||
import type { EmbedderProvider } from "@roo/embeddingModels"
|
||||
import type { IndexingStatus } from "@roo/ExtensionMessage"
|
||||
import { type IndexingStatus, type EmbedderProvider, CODEBASE_INDEX_DEFAULTS } from "@roo-code/types"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ChevronDown, OctagonX } from "lucide-react"
|
|||
|
||||
import { type ExtensionMessage, type CommandExecutionStatus, commandExecutionStatusSchema } from "@roo-code/types"
|
||||
|
||||
import { safeJsonParse } from "@roo/safeJsonParse"
|
||||
import { safeJsonParse } from "@roo/core"
|
||||
import { COMMAND_OUTPUT_STRING } from "@roo/combineCommandSequences"
|
||||
import { parseCommand } from "@roo/parse-command"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useState, useEffect, useMemo } from "react"
|
||||
import { Database } from "lucide-react"
|
||||
|
||||
import type { IndexingStatus, IndexingStatusUpdateMessage } from "@roo-code/types"
|
||||
|
||||
import { cn } from "@src/lib/utils"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
|
||||
import type { IndexingStatus, IndexingStatusUpdateMessage } from "@roo/ExtensionMessage"
|
||||
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { PopoverTrigger, StandardTooltip, Button } from "@src/components/ui"
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,18 @@ import { Server, ChevronDown } from "lucide-react"
|
|||
import { useEvent } from "react-use"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { type ExtensionMessage, type McpExecutionStatus, mcpExecutionStatusSchema } from "@roo-code/types"
|
||||
import {
|
||||
type ExtensionMessage,
|
||||
type ClineAskUseMcpServer,
|
||||
type McpExecutionStatus,
|
||||
mcpExecutionStatusSchema,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { safeJsonParse } from "@roo/core"
|
||||
|
||||
import { cn } from "@src/lib/utils"
|
||||
import { Button } from "@src/components/ui"
|
||||
|
||||
import { ClineAskUseMcpServer } from "../../../../src/shared/ExtensionMessage"
|
||||
import { safeJsonParse } from "../../../../src/shared/safeJsonParse"
|
||||
import CodeBlock from "../common/CodeBlock"
|
||||
import McpToolRow from "../mcp/McpToolRow"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue