From 31d2d17847abdb30cf1f7430aa5ef63df9f3eea9 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 28 Feb 2025 01:15:44 -0800 Subject: [PATCH 01/40] Fix maxTokens defaults for Claude 3.7 Sonnet models --- .changeset/tasty-grapes-suffer.md | 5 ++++ src/api/providers/openrouter.ts | 2 +- src/shared/api.ts | 6 ++--- .../src/components/settings/ApiOptions.tsx | 2 -- .../components/settings/ThinkingBudget.tsx | 12 +++------ .../settings/__tests__/ApiOptions.test.tsx | 2 -- .../__tests__/ThinkingBudget.test.tsx | 26 +++---------------- 7 files changed, 15 insertions(+), 40 deletions(-) create mode 100644 .changeset/tasty-grapes-suffer.md diff --git a/.changeset/tasty-grapes-suffer.md b/.changeset/tasty-grapes-suffer.md new file mode 100644 index 0000000000..7382b38c77 --- /dev/null +++ b/.changeset/tasty-grapes-suffer.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Fix maxTokens defaults for Claude 3.7 Sonnet models diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 82c02e20a7..70c40c2c27 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -278,7 +278,7 @@ export async function getOpenRouterModels() { modelInfo.supportsPromptCache = true modelInfo.cacheWritesPrice = 3.75 modelInfo.cacheReadsPrice = 0.3 - modelInfo.maxTokens = 64_000 + modelInfo.maxTokens = rawModel.id === "anthropic/claude-3.7-sonnet:thinking" ? 64_000 : 16_384 break case rawModel.id.startsWith("anthropic/claude-3.5-sonnet-20240620"): modelInfo.supportsPromptCache = true diff --git a/src/shared/api.ts b/src/shared/api.ts index f88bb5e8b5..99e2986e88 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -111,7 +111,7 @@ export const anthropicModels = { thinking: true, }, "claude-3-7-sonnet-20250219": { - maxTokens: 64_000, + maxTokens: 16_384, contextWindow: 200_000, supportsImages: true, supportsComputerUse: true, @@ -437,7 +437,7 @@ export type VertexModelId = keyof typeof vertexModels export const vertexDefaultModelId: VertexModelId = "claude-3-7-sonnet@20250219" export const vertexModels = { "claude-3-7-sonnet@20250219:thinking": { - maxTokens: 64000, + maxTokens: 64_000, contextWindow: 200_000, supportsImages: true, supportsComputerUse: true, @@ -449,7 +449,7 @@ export const vertexModels = { thinking: true, }, "claude-3-7-sonnet@20250219": { - maxTokens: 8192, + maxTokens: 16_384, contextWindow: 200_000, supportsImages: true, supportsComputerUse: true, diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 42ac5cdcb3..6f0dba9f00 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -7,7 +7,6 @@ import * as vscodemodels from "vscode" import { ApiConfiguration, ModelInfo, - ApiProvider, anthropicDefaultModelId, anthropicModels, azureOpenAiDefaultApiVersion, @@ -1385,7 +1384,6 @@ const ApiOptions = ({ apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} modelInfo={selectedModelInfo} - provider={selectedProvider as ApiProvider} /> (field: K, value: ApiConfiguration[K]) => void modelInfo?: ModelInfo - provider?: ApiProvider } -export const ThinkingBudget = ({ - apiConfiguration, - setApiConfigurationField, - modelInfo, - provider, -}: ThinkingBudgetProps) => { - const tokens = apiConfiguration?.modelMaxTokens || modelInfo?.maxTokens || 64_000 +export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, modelInfo }: ThinkingBudgetProps) => { + const tokens = apiConfiguration?.modelMaxTokens || 16_384 const tokensMin = 8192 const tokensMax = modelInfo?.maxTokens || 64_000 diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx index 06ed95585a..0b1fb28498 100644 --- a/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiOptions.test.tsx @@ -92,7 +92,6 @@ describe("ApiOptions", () => { }) expect(screen.getByTestId("thinking-budget")).toBeInTheDocument() - expect(screen.getByTestId("thinking-budget")).toHaveAttribute("data-provider", "anthropic") }) it("should show ThinkingBudget for Vertex models that support thinking", () => { @@ -104,7 +103,6 @@ describe("ApiOptions", () => { }) expect(screen.getByTestId("thinking-budget")).toBeInTheDocument() - expect(screen.getByTestId("thinking-budget")).toHaveAttribute("data-provider", "vertex") }) it("should not show ThinkingBudget for models that don't support thinking", () => { diff --git a/webview-ui/src/components/settings/__tests__/ThinkingBudget.test.tsx b/webview-ui/src/components/settings/__tests__/ThinkingBudget.test.tsx index 212316ea9a..1e14e94524 100644 --- a/webview-ui/src/components/settings/__tests__/ThinkingBudget.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ThinkingBudget.test.tsx @@ -1,7 +1,6 @@ -import React from "react" import { render, screen, fireEvent } from "@testing-library/react" import { ThinkingBudget } from "../ThinkingBudget" -import { ApiProvider, ModelInfo } from "../../../../../src/shared/api" +import { ModelInfo } from "../../../../../src/shared/api" // Mock Slider component jest.mock("@/components/ui", () => ({ @@ -25,11 +24,11 @@ describe("ThinkingBudget", () => { supportsPromptCache: true, supportsImages: true, } + const defaultProps = { apiConfiguration: {}, setApiConfigurationField: jest.fn(), modelInfo: mockModelInfo, - provider: "anthropic" as ApiProvider, } beforeEach(() => { @@ -60,7 +59,7 @@ describe("ThinkingBudget", () => { expect(screen.getAllByTestId("slider")).toHaveLength(2) }) - it("should use modelMaxThinkingTokens field for Anthropic provider", () => { + it("should update modelMaxThinkingTokens", () => { const setApiConfigurationField = jest.fn() render( @@ -68,25 +67,6 @@ describe("ThinkingBudget", () => { {...defaultProps} apiConfiguration={{ modelMaxThinkingTokens: 4096 }} setApiConfigurationField={setApiConfigurationField} - provider="anthropic" - />, - ) - - const sliders = screen.getAllByTestId("slider") - fireEvent.change(sliders[1], { target: { value: "5000" } }) - - expect(setApiConfigurationField).toHaveBeenCalledWith("modelMaxThinkingTokens", 5000) - }) - - it("should use modelMaxThinkingTokens field for Vertex provider", () => { - const setApiConfigurationField = jest.fn() - - render( - , ) From 0a81853d84e43ca92718166db6d07ea984889a39 Mon Sep 17 00:00:00 2001 From: yansheng3 Date: Fri, 28 Feb 2025 19:06:43 +0800 Subject: [PATCH 02/40] Optimize Task ID Initialization Order Signed-off-by: yansheng3 --- src/core/Cline.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 00897eecf4..3127d3d38b 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -148,7 +148,8 @@ export class Cline { throw new Error("Either historyItem or task/images must be provided") } - this.taskId = crypto.randomUUID() + this.taskId = historyItem ? historyItem.id : crypto.randomUUID() + this.apiConfiguration = apiConfiguration this.api = buildApiHandler(apiConfiguration) this.terminalManager = new TerminalManager() @@ -161,10 +162,6 @@ export class Cline { this.diffViewProvider = new DiffViewProvider(cwd) this.enableCheckpoints = enableCheckpoints ?? false - if (historyItem) { - this.taskId = historyItem.id - } - // Initialize diffStrategy based on current state this.updateDiffStrategy(Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.DIFF_STRATEGY)) From 2773d391535db0f03dedc1df13af809b3f23c500 Mon Sep 17 00:00:00 2001 From: samir-nimbly <112695483+samir-nimbly@users.noreply.github.com> Date: Fri, 28 Feb 2025 20:05:31 +0530 Subject: [PATCH 03/40] fix: dropdown hover color fix for white themes --- webview-ui/src/components/chat/ContextMenu.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx index 2bb7a8ee68..20bd5222f6 100644 --- a/webview-ui/src/components/chat/ContextMenu.tsx +++ b/webview-ui/src/components/chat/ContextMenu.tsx @@ -187,10 +187,12 @@ const ContextMenu: React.FC = ({ display: "flex", alignItems: "center", justifyContent: "space-between", - backgroundColor: - index === selectedIndex && isOptionSelectable(option) - ? "var(--vscode-list-activeSelectionBackground)" - : "", + ...(index === selectedIndex && isOptionSelectable(option) + ? { + backgroundColor: "var(--vscode-list-activeSelectionBackground)", + color: "var(--vscode-list-activeSelectionForeground)", + } + : {}), }} onMouseEnter={() => isOptionSelectable(option) && setSelectedIndex(index)}>
Date: Fri, 28 Feb 2025 10:30:37 -0500 Subject: [PATCH 04/40] Update pull_request_template.md --- .github/pull_request_template.md | 42 +++++++++++++++----------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7ee8bb98ad..85b5c9063c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,37 +1,35 @@ - +## Context -## Description + -## Type of change +## Implementation - + - +## Screenshots -## Checklist: +| before | after | +| ------ | ----- | +| | | - +## How to Test -- [ ] My code follows the patterns of this project -- [ ] I have performed a self-review of my own code -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation + +A "How To Test" section can look something like this: -## Related Issues +- Sign in with a user with tracks +- Activate `show_awesome_cat_gifs` feature (add `?feature.show_awesome_cat_gifs=1` to your URL) +- You should see a GIF with cats dancing - +--> -## Reviewers +## Get in Touch - + From 87785bb7504710ddda21bb3693090e6a546a77ff Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 28 Feb 2025 10:35:38 -0500 Subject: [PATCH 05/40] Update pull_request_template.md --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 85b5c9063c..de7e461cb9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -32,4 +32,4 @@ A "How To Test" section can look something like this: ## Get in Touch - + From 4b66ce8255b594a94903b3859a29ad25244c3f3f Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 28 Feb 2025 09:38:48 -0800 Subject: [PATCH 06/40] Improve terminal command execution output parsing --- src/core/Cline.ts | 28 +- src/integrations/terminal/TerminalManager.ts | 193 +++++++- src/integrations/terminal/TerminalProcess.ts | 427 ++++++++++++------ src/integrations/terminal/TerminalRegistry.ts | 30 ++ .../__tests__/TerminalProcess.test.ts | 163 +++---- .../__tests__/TerminalRegistry.test.ts | 5 +- src/shared/combineCommandSequences.ts | 2 +- 7 files changed, 591 insertions(+), 257 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 00897eecf4..fab181b86e 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -22,7 +22,7 @@ import { everyLineHasLineNumbers, truncateOutput, } from "../integrations/misc/extract-text" -import { TerminalManager } from "../integrations/terminal/TerminalManager" +import { TerminalManager, ExitCodeDetails } from "../integrations/terminal/TerminalManager" import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" import { listFiles } from "../services/glob/list-files" import { regexSearchFiles } from "../services/ripgrep" @@ -834,10 +834,21 @@ export class Cline { }) let completed = false - process.once("completed", () => { + let exitDetails: ExitCodeDetails | undefined + process.once("completed", (output?: string) => { + // Use provided output if available, otherwise keep existing result. + if (output) { + lines = output.split("\n") + } completed = true }) + process.once("shell_execution_complete", (id: number, details: ExitCodeDetails) => { + if (id === terminalInfo.id) { + exitDetails = details + } + }) + process.once("no_shell_integration", async () => { await this.say("shell_integration_warning") }) @@ -869,7 +880,18 @@ export class Cline { } if (completed) { - return [false, `Command executed.${result.length > 0 ? `\nOutput:\n${result}` : ""}`] + let exitStatus = "No exit code available" + if (exitDetails !== undefined) { + if (exitDetails.signal) { + exitStatus = `Process terminated by signal ${exitDetails.signal} (${exitDetails.signalName})` + if (exitDetails.coreDumpPossible) { + exitStatus += " - core dump possible" + } + } else { + exitStatus = `Exit code: ${exitDetails.exitCode}` + } + } + return [false, `Command executed. ${exitStatus}${result.length > 0 ? `\nOutput:\n${result}` : ""}`] } else { return [ false, diff --git a/src/integrations/terminal/TerminalManager.ts b/src/integrations/terminal/TerminalManager.ts index d5496e20fb..a55f7867d4 100644 --- a/src/integrations/terminal/TerminalManager.ts +++ b/src/integrations/terminal/TerminalManager.ts @@ -70,6 +70,15 @@ Interestingly, some environments like Cursor enable these APIs even without the This approach allows us to leverage advanced features when available while ensuring broad compatibility. */ declare module "vscode" { + // https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L7442 + // interface Terminal { + // shellIntegration?: { + // cwd?: vscode.Uri + // executeCommand?: (command: string) => { + // read: () => AsyncIterable + // } + // } + // } // https://github.com/microsoft/vscode/blob/f0417069c62e20f3667506f4b7e53ca0004b4e3e/src/vscode-dts/vscode.d.ts#L10794 interface Window { onDidStartTerminalShellExecution?: ( @@ -77,17 +86,19 @@ declare module "vscode" { thisArgs?: any, disposables?: vscode.Disposable[], ) => vscode.Disposable + onDidEndTerminalShellExecution?: ( + listener: (e: { terminal: vscode.Terminal; exitCode?: number; shellType?: string }) => any, + thisArgs?: any, + disposables?: vscode.Disposable[], + ) => vscode.Disposable } } -// Extend the Terminal type to include our custom properties -type ExtendedTerminal = vscode.Terminal & { - shellIntegration?: { - cwd?: vscode.Uri - executeCommand?: (command: string) => { - read: () => AsyncIterable - } - } +export interface ExitCodeDetails { + exitCode: number | undefined + signal?: number | undefined + signalName?: string + coreDumpPossible?: boolean } export class TerminalManager { @@ -95,18 +106,156 @@ export class TerminalManager { private processes: Map = new Map() private disposables: vscode.Disposable[] = [] + private interpretExitCode(exitCode: number | undefined): ExitCodeDetails { + if (exitCode === undefined) { + return { exitCode } + } + + if (exitCode <= 128) { + return { exitCode } + } + + const signal = exitCode - 128 + const signals: Record = { + // Standard signals + 1: "SIGHUP", + 2: "SIGINT", + 3: "SIGQUIT", + 4: "SIGILL", + 5: "SIGTRAP", + 6: "SIGABRT", + 7: "SIGBUS", + 8: "SIGFPE", + 9: "SIGKILL", + 10: "SIGUSR1", + 11: "SIGSEGV", + 12: "SIGUSR2", + 13: "SIGPIPE", + 14: "SIGALRM", + 15: "SIGTERM", + 16: "SIGSTKFLT", + 17: "SIGCHLD", + 18: "SIGCONT", + 19: "SIGSTOP", + 20: "SIGTSTP", + 21: "SIGTTIN", + 22: "SIGTTOU", + 23: "SIGURG", + 24: "SIGXCPU", + 25: "SIGXFSZ", + 26: "SIGVTALRM", + 27: "SIGPROF", + 28: "SIGWINCH", + 29: "SIGIO", + 30: "SIGPWR", + 31: "SIGSYS", + + // Real-time signals base + 34: "SIGRTMIN", + + // SIGRTMIN+n signals + 35: "SIGRTMIN+1", + 36: "SIGRTMIN+2", + 37: "SIGRTMIN+3", + 38: "SIGRTMIN+4", + 39: "SIGRTMIN+5", + 40: "SIGRTMIN+6", + 41: "SIGRTMIN+7", + 42: "SIGRTMIN+8", + 43: "SIGRTMIN+9", + 44: "SIGRTMIN+10", + 45: "SIGRTMIN+11", + 46: "SIGRTMIN+12", + 47: "SIGRTMIN+13", + 48: "SIGRTMIN+14", + 49: "SIGRTMIN+15", + + // SIGRTMAX-n signals + 50: "SIGRTMAX-14", + 51: "SIGRTMAX-13", + 52: "SIGRTMAX-12", + 53: "SIGRTMAX-11", + 54: "SIGRTMAX-10", + 55: "SIGRTMAX-9", + 56: "SIGRTMAX-8", + 57: "SIGRTMAX-7", + 58: "SIGRTMAX-6", + 59: "SIGRTMAX-5", + 60: "SIGRTMAX-4", + 61: "SIGRTMAX-3", + 62: "SIGRTMAX-2", + 63: "SIGRTMAX-1", + 64: "SIGRTMAX", + } + + // These signals may produce core dumps: + // SIGQUIT, SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV + const coreDumpPossible = new Set([3, 4, 6, 7, 8, 11]) + + return { + exitCode, + signal, + signalName: signals[signal] || `Unknown Signal (${signal})`, + coreDumpPossible: coreDumpPossible.has(signal), + } + } + constructor() { - let disposable: vscode.Disposable | undefined + let startDisposable: vscode.Disposable | undefined + let endDisposable: vscode.Disposable | undefined try { - disposable = (vscode.window as vscode.Window).onDidStartTerminalShellExecution?.(async (e) => { - // Creating a read stream here results in a more consistent output. This is most obvious when running the `date` command. - e?.execution?.read() + // onDidStartTerminalShellExecution + startDisposable = (vscode.window as vscode.Window).onDidStartTerminalShellExecution?.(async (e) => { + // Get a handle to the stream as early as possible: + const stream = e?.execution.read() + const terminalInfo = TerminalRegistry.getTerminalInfoByTerminal(e.terminal) + if (stream && terminalInfo) { + const process = this.processes.get(terminalInfo.id) + if (process) { + terminalInfo.stream = stream + terminalInfo.running = true + terminalInfo.streamClosed = false + process.emit("stream_available", terminalInfo.id, stream) + } + } else { + console.error("[TerminalManager] Stream failed, not registered for terminal") + } + + console.info("[TerminalManager] Shell execution started:", { + hasExecution: !!e?.execution, + command: e?.execution?.commandLine?.value, + terminalId: terminalInfo?.id, + }) + }) + + // onDidEndTerminalShellExecution + endDisposable = (vscode.window as vscode.Window).onDidEndTerminalShellExecution?.(async (e) => { + const exitDetails = this.interpretExitCode(e?.exitCode) + console.info("[TerminalManager] Shell execution ended:", { + ...exitDetails, + }) + + // Signal completion to any waiting processes + for (const id of this.terminalIds) { + const info = TerminalRegistry.getTerminal(id) + if (info && info.terminal === e.terminal) { + info.running = false + const process = this.processes.get(id) + if (process) { + process.emit("shell_execution_complete", id, exitDetails) + } + break + } + } }) } catch (error) { - // console.error("Error setting up onDidEndTerminalShellExecution", error) + console.error("[TerminalManager] Error setting up shell execution handlers:", error) } - if (disposable) { - this.disposables.push(disposable) + if (startDisposable) { + this.disposables.push(startDisposable) + } + if (endDisposable) { + this.disposables.push(endDisposable) } } @@ -140,19 +289,16 @@ export class TerminalManager { }) // if shell integration is already active, run the command immediately - const terminal = terminalInfo.terminal as ExtendedTerminal - if (terminal.shellIntegration) { + if (terminalInfo.terminal.shellIntegration) { process.waitForShellIntegration = false - process.run(terminal, command) + process.run(terminalInfo.terminal, command) } else { // docs recommend waiting 3s for shell integration to activate - pWaitFor(() => (terminalInfo.terminal as ExtendedTerminal).shellIntegration !== undefined, { - timeout: 4000, - }).finally(() => { + pWaitFor(() => terminalInfo.terminal.shellIntegration !== undefined, { timeout: 4000 }).finally(() => { const existingProcess = this.processes.get(terminalInfo.id) if (existingProcess && existingProcess.waitForShellIntegration) { existingProcess.waitForShellIntegration = false - existingProcess.run(terminal, command) + existingProcess.run(terminalInfo.terminal, command) } }) } @@ -168,8 +314,7 @@ export class TerminalManager { if (t.busy) { return false } - const terminal = t.terminal as ExtendedTerminal - const terminalCwd = terminal.shellIntegration?.cwd // one of cline's commands could have changed the cwd of the terminal + const terminalCwd = t.terminal.shellIntegration?.cwd // one of cline's commands could have changed the cwd of the terminal if (!terminalCwd) { return false } diff --git a/src/integrations/terminal/TerminalProcess.ts b/src/integrations/terminal/TerminalProcess.ts index 5597350db3..99ef215e78 100644 --- a/src/integrations/terminal/TerminalProcess.ts +++ b/src/integrations/terminal/TerminalProcess.ts @@ -1,13 +1,24 @@ import { EventEmitter } from "events" import stripAnsi from "strip-ansi" import * as vscode from "vscode" +import { inspect } from "util" + +import { ExitCodeDetails } from "./TerminalManager" +import { TerminalInfo, TerminalRegistry } from "./TerminalRegistry" export interface TerminalProcessEvents { line: [line: string] continue: [] - completed: [] + completed: [output?: string] error: [error: Error] no_shell_integration: [] + /** + * Emitted when a shell execution completes + * @param id The terminal ID + * @param exitDetails Contains exit code and signal information if process was terminated by signal + */ + shell_execution_complete: [id: number, exitDetails: ExitCodeDetails] + stream_available: [id: number, stream: AsyncIterable] } // how long to wait after a process outputs anything before we consider it "cool" again @@ -17,104 +28,99 @@ const PROCESS_HOT_TIMEOUT_COMPILING = 15_000 export class TerminalProcess extends EventEmitter { waitForShellIntegration: boolean = true private isListening: boolean = true - private buffer: string = "" + private terminalInfo: TerminalInfo | undefined + private lastEmitTime_ms: number = 0 private fullOutput: string = "" private lastRetrievedIndex: number = 0 isHot: boolean = false private hotTimer: NodeJS.Timeout | null = null - // constructor() { - // super() - async run(terminal: vscode.Terminal, command: string) { if (terminal.shellIntegration && terminal.shellIntegration.executeCommand) { - const execution = terminal.shellIntegration.executeCommand(command) - const stream = execution.read() - // todo: need to handle errors - let isFirstChunk = true - let didOutputNonCommand = false - let didEmitEmptyLine = false + // Get terminal info to access stream + const terminalInfo = TerminalRegistry.getTerminalInfoByTerminal(terminal) + if (!terminalInfo) { + console.error("[TerminalProcess] Terminal not found in registry") + this.emit("no_shell_integration") + this.emit("completed") + this.emit("continue") + return + } + + // When executeCommand() is called, onDidStartTerminalShellExecution will fire in TerminalManager + // which creates a new stream via execution.read() and emits 'stream_available' + const streamAvailable = new Promise>((resolve) => { + this.once("stream_available", (id: number, stream: AsyncIterable) => { + if (id === terminalInfo.id) { + resolve(stream) + } + }) + }) + + // Create promise that resolves when shell execution completes for this terminal + const shellExecutionComplete = new Promise((resolve) => { + this.once("shell_execution_complete", (id: number, exitDetails: ExitCodeDetails) => { + if (id === terminalInfo.id) { + resolve(exitDetails) + } + }) + }) + + // getUnretrievedOutput needs to know if streamClosed, so store this for later + this.terminalInfo = terminalInfo + + // Execute command + terminal.shellIntegration.executeCommand(command) + this.isHot = true + + // Wait for stream to be available + const stream = await streamAvailable + + let preOutput = "" + let commandOutputStarted = false + + /* + * Extract clean output from raw accumulated output. FYI: + * ]633 is a custom sequence number used by VSCode shell integration: + * - OSC 633 ; A ST - Mark prompt start + * - OSC 633 ; B ST - Mark prompt end + * - OSC 633 ; C ST - Mark pre-execution (start of command output) + * - OSC 633 ; D [; ] ST - Mark execution finished with optional exit code + * - OSC 633 ; E ; [; ] ST - Explicitly set command line with optional nonce + */ + + // Process stream data for await (let data of stream) { - // 1. Process chunk and remove artifacts - if (isFirstChunk) { - /* - The first chunk we get from this stream needs to be processed to be more human readable, ie remove vscode's custom escape sequences and identifiers, removing duplicate first char bug, etc. - */ - - // bug where sometimes the command output makes its way into vscode shell integration metadata - /* - ]633 is a custom sequence number used by VSCode shell integration: - - OSC 633 ; A ST - Mark prompt start - - OSC 633 ; B ST - Mark prompt end - - OSC 633 ; C ST - Mark pre-execution (start of command output) - - OSC 633 ; D [; ] ST - Mark execution finished with optional exit code - - OSC 633 ; E ; [; ] ST - Explicitly set command line with optional nonce - */ - // if you print this data you might see something like "eecho hello worldo hello world;5ba85d14-e92a-40c4-b2fd-71525581eeb0]633;C" but this is actually just a bunch of escape sequences, ignore up to the first ;C - /* ddateb15026-6a64-40db-b21f-2a621a9830f0]633;CTue Sep 17 06:37:04 EDT 2024 % ]633;D;0]633;P;Cwd=/Users/saoud/Repositories/test */ - // Gets output between ]633;C (command start) and ]633;D (command end) - const outputBetweenSequences = this.removeLastLineArtifacts( - data.match(/\]633;C([\s\S]*?)\]633;D/)?.[1] || "", - ).trim() - - // Once we've retrieved any potential output between sequences, we can remove everything up to end of the last sequence - // https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st - const vscodeSequenceRegex = /\x1b\]633;.[^\x07]*\x07/g - const lastMatch = [...data.matchAll(vscodeSequenceRegex)].pop() - if (lastMatch && lastMatch.index !== undefined) { - data = data.slice(lastMatch.index + lastMatch[0].length) + // Check for command output start marker + if (!commandOutputStarted) { + preOutput += data + const match = this.matchAfterVsceStartMarkers(data) + if (match !== undefined) { + commandOutputStarted = true + data = match + this.fullOutput = "" // Reset fullOutput when command actually starts + } else { + continue } - // Place output back after removing vscode sequences - if (outputBetweenSequences) { - data = outputBetweenSequences + "\n" + data - } - // remove ansi - data = stripAnsi(data) - // Split data by newlines - let lines = data ? data.split("\n") : [] - // Remove non-human readable characters from the first line - if (lines.length > 0) { - lines[0] = lines[0].replace(/[^\x20-\x7E]/g, "") - } - // Check if first two characters are the same, if so remove the first character - if (lines.length > 0 && lines[0].length >= 2 && lines[0][0] === lines[0][1]) { - lines[0] = lines[0].slice(1) - } - // Remove everything up to the first alphanumeric character for first two lines - if (lines.length > 0) { - lines[0] = lines[0].replace(/^[^a-zA-Z0-9]*/, "") - } - if (lines.length > 1) { - lines[1] = lines[1].replace(/^[^a-zA-Z0-9]*/, "") - } - // Join lines back - data = lines.join("\n") - isFirstChunk = false - } else { - data = stripAnsi(data) } - // first few chunks could be the command being echoed back, so we must ignore - // note this means that 'echo' commands wont work - if (!didOutputNonCommand) { - const lines = data.split("\n") - for (let i = 0; i < lines.length; i++) { - if (command.includes(lines[i].trim())) { - lines.splice(i, 1) - i-- // Adjust index after removal - } else { - didOutputNonCommand = true - break - } - } - data = lines.join("\n") + // Command output started, accumulate data without filtering. + // notice to future programmers: do not add escape sequence + // filtering here: fullOutput cannot change in length (see getUnretrievedOutput), + // and chunks may not be complete so you cannot rely on detecting or removing escape sequences mid-stream. + this.fullOutput += data + + // For non-immediately returning commands we want to show loading spinner + // right away but this wouldnt happen until it emits a line break, so + // as soon as we get any output we emit to let webview know to show spinner + const now = Date.now() + if (this.isListening && (now - this.lastEmitTime_ms > 100 || this.lastEmitTime_ms === 0)) { + this.emitRemainingBufferIfListening() + this.lastEmitTime_ms = now } - // FIXME: right now it seems that data chunks returned to us from the shell integration stream contains random commas, which from what I can tell is not the expected behavior. There has to be a better solution here than just removing all commas. - data = data.replace(/,/g, "") - - // 2. Set isHot depending on the command - // Set to hot to stall API requests until terminal is cool again + // 2. Set isHot depending on the command. + // This stalls API requests until terminal is cool again. this.isHot = true if (this.hotTimer) { clearTimeout(this.hotTimer) @@ -144,21 +150,37 @@ export class TerminalProcess extends EventEmitter { }, isCompiling ? PROCESS_HOT_TIMEOUT_COMPILING : PROCESS_HOT_TIMEOUT_NORMAL, ) - - // For non-immediately returning commands we want to show loading spinner right away but this wouldnt happen until it emits a line break, so as soon as we get any output we emit "" to let webview know to show spinner - if (!didEmitEmptyLine && !this.fullOutput && data) { - this.emit("line", "") // empty line to indicate start of command output stream - didEmitEmptyLine = true - } - - this.fullOutput += data - if (this.isListening) { - this.emitIfEol(data) - this.lastRetrievedIndex = this.fullOutput.length - this.buffer.length - } } - this.emitRemainingBufferIfListening() + // Set streamClosed immediately after stream ends + if (this.terminalInfo) { + this.terminalInfo.streamClosed = true + } + + // Wait for shell execution to complete and handle exit details + const exitDetails = await shellExecutionComplete + this.isHot = false + + if (commandOutputStarted) { + // Emit any remaining output before completing + this.emitRemainingBufferIfListening() + } else { + console.error( + "[Terminal Process] VSCE output start escape sequence (]633;C or ]133;C) not received! VSCE Bug? preOutput: " + + inspect(preOutput, { colors: false, breakLength: Infinity }), + ) + } + + // console.debug("[Terminal Process] raw output: " + inspect(output, { colors: false, breakLength: Infinity })) + + // fullOutput begins after C marker so we only need to trim off D marker + // (if D exists, see VSCode bug# 237208): + const match = this.matchBeforeVsceEndMarkers(this.fullOutput) + if (match !== undefined) { + this.fullOutput = match + } + + // console.debug(`[Terminal Process] processed output via ${matchSource}: ` + inspect(output, { colors: false, breakLength: Infinity })) // for now we don't want this delaying requests since we don't send diagnostics automatically anymore (previous: "even though the command is finished, we still want to consider it 'hot' in case so that api request stalls to let diagnostics catch up") if (this.hotTimer) { @@ -166,7 +188,7 @@ export class TerminalProcess extends EventEmitter { } this.isHot = false - this.emit("completed") + this.emit("completed", this.removeEscapeSequences(this.fullOutput)) this.emit("continue") } else { terminal.sendText(command, true) @@ -182,29 +204,12 @@ export class TerminalProcess extends EventEmitter { } } - // Inspired by https://github.com/sindresorhus/execa/blob/main/lib/transform/split.js - private emitIfEol(chunk: string) { - this.buffer += chunk - let lineEndIndex: number - while ((lineEndIndex = this.buffer.indexOf("\n")) !== -1) { - let line = this.buffer.slice(0, lineEndIndex).trimEnd() // removes trailing \r - // Remove \r if present (for Windows-style line endings) - // if (line.endsWith("\r")) { - // line = line.slice(0, -1) - // } - this.emit("line", line) - this.buffer = this.buffer.slice(lineEndIndex + 1) - } - } - private emitRemainingBufferIfListening() { - if (this.buffer && this.isListening) { - const remainingBuffer = this.removeLastLineArtifacts(this.buffer) - if (remainingBuffer) { + if (this.isListening) { + const remainingBuffer = this.getUnretrievedOutput() + if (remainingBuffer !== "") { this.emit("line", remainingBuffer) } - this.buffer = "" - this.lastRetrievedIndex = this.fullOutput.length } } @@ -215,22 +220,180 @@ export class TerminalProcess extends EventEmitter { this.emit("continue") } + // Returns complete lines with their carriage returns. + // The final line may lack a carriage return if the program didn't send one. getUnretrievedOutput(): string { - const unretrieved = this.fullOutput.slice(this.lastRetrievedIndex) - this.lastRetrievedIndex = this.fullOutput.length - return this.removeLastLineArtifacts(unretrieved) + // Get raw unretrieved output + let outputToProcess = this.fullOutput.slice(this.lastRetrievedIndex) + + // Check for VSCE command end markers + const index633 = outputToProcess.indexOf("\x1b]633;D") + const index133 = outputToProcess.indexOf("\x1b]133;D") + let endIndex = -1 + + if (index633 !== -1 && index133 !== -1) { + endIndex = Math.min(index633, index133) + } else if (index633 !== -1) { + endIndex = index633 + } else if (index133 !== -1) { + endIndex = index133 + } + + // If no end markers were found yet (possibly due to VSCode bug#237208): + // For active streams: return only complete lines (up to last \n). + // For closed streams: return all remaining content. + if (endIndex === -1) { + if (!this.terminalInfo?.streamClosed) { + // Stream still running - only process complete lines + endIndex = outputToProcess.lastIndexOf("\n") + if (endIndex === -1) { + // No complete lines + return "" + } + + // Include carriage return + endIndex++ + } else { + // Stream closed - process all remaining output + endIndex = outputToProcess.length + } + } + + // Update index and slice output + this.lastRetrievedIndex += endIndex + outputToProcess = outputToProcess.slice(0, endIndex) + + // Clean and return output + return this.removeEscapeSequences(outputToProcess) } - // some processing to remove artifacts like '%' at the end of the buffer (it seems that since vsode uses % at the beginning of newlines in terminal, it makes its way into the stream) - // This modification will remove '%', '$', '#', or '>' followed by optional whitespace - removeLastLineArtifacts(output: string) { - const lines = output.trimEnd().split("\n") - if (lines.length > 0) { - const lastLine = lines[lines.length - 1] - // Remove prompt characters and trailing whitespace from the last line - lines[lines.length - 1] = lastLine.replace(/[%$#>]\s*$/, "") + private stringIndexMatch( + data: string, + prefix?: string, + suffix?: string, + bell: string = "\x07", + ): string | undefined { + let startIndex: number + let endIndex: number + let prefixLength: number + + if (prefix === undefined) { + startIndex = 0 + prefixLength = 0 + } else { + startIndex = data.indexOf(prefix) + if (startIndex === -1) { + return undefined + } + if (bell.length > 0) { + // Find the bell character after the prefix + const bellIndex = data.indexOf(bell, startIndex + prefix.length) + if (bellIndex === -1) { + return undefined + } + + const distanceToBell = bellIndex - startIndex + + prefixLength = distanceToBell + bell.length + } else { + prefixLength = prefix.length + } } - return lines.join("\n").trimEnd() + + const contentStart = startIndex + prefixLength + + if (suffix === undefined) { + // When suffix is undefined, match to end + endIndex = data.length + } else { + endIndex = data.indexOf(suffix, contentStart) + if (endIndex === -1) { + return undefined + } + } + + return data.slice(contentStart, endIndex) + } + + // Removes ANSI escape sequences and VSCode-specific terminal control codes from output. + // While stripAnsi handles most ANSI codes, VSCode's shell integration adds custom + // escape sequences (OSC 633) that need special handling. These sequences control + // terminal features like marking command start/end and setting prompts. + // + // This method could be extended to handle other escape sequences, but any additions + // should be carefully considered to ensure they only remove control codes and don't + // alter the actual content or behavior of the output stream. + private removeEscapeSequences(str: string): string { + return stripAnsi(str.replace(/\x1b\]633;[^\x07]+\x07/gs, "").replace(/\x1b\]133;[^\x07]+\x07/gs, "")) + } + + /** + * Helper function to match VSCode shell integration start markers (C). + * Looks for content after ]633;C or ]133;C markers. + * If both exist, takes the content after the last marker found. + */ + private matchAfterVsceStartMarkers(data: string): string | undefined { + return this.matchVsceMarkers(data, "\x1b]633;C", "\x1b]133;C", undefined, undefined) + } + + /** + * Helper function to match VSCode shell integration end markers (D). + * Looks for content before ]633;D or ]133;D markers. + * If both exist, takes the content before the first marker found. + */ + private matchBeforeVsceEndMarkers(data: string): string | undefined { + return this.matchVsceMarkers(data, undefined, undefined, "\x1b]633;D", "\x1b]133;D") + } + + /** + * Handles VSCode shell integration markers for command output: + * + * For C (Command Start): + * - Looks for content after ]633;C or ]133;C markers + * - These markers indicate the start of command output + * - If both exist, takes the content after the last marker found + * - This ensures we get the actual command output after any shell integration prefixes + * + * For D (Command End): + * - Looks for content before ]633;D or ]133;D markers + * - These markers indicate command completion + * - If both exist, takes the content before the first marker found + * - This ensures we don't include shell integration suffixes in the output + * + * In both cases, checks 633 first since it's more commonly used in VSCode shell integration + * + * @param data The string to search for markers in + * @param prefix633 The 633 marker to match after (for C markers) + * @param prefix133 The 133 marker to match after (for C markers) + * @param suffix633 The 633 marker to match before (for D markers) + * @param suffix133 The 133 marker to match before (for D markers) + * @returns The content between/after markers, or undefined if no markers found + * + * Note: Always makes exactly 2 calls to stringIndexMatch regardless of match results. + * Using string indexOf matching is ~500x faster than regular expressions, so even + * matching twice is still very efficient comparatively. + */ + private matchVsceMarkers( + data: string, + prefix633: string | undefined, + prefix133: string | undefined, + suffix633: string | undefined, + suffix133: string | undefined, + ): string | undefined { + // Support both VSCode shell integration markers (633 and 133) + // Check 633 first since it's more commonly used in VSCode shell integration + let match133: string | undefined + const match633 = this.stringIndexMatch(data, prefix633, suffix633) + + // Must check explicitly for undefined because stringIndexMatch can return empty strings + // that are valid matches (e.g., when a marker exists but has no content between markers) + if (match633 !== undefined) { + match133 = this.stringIndexMatch(match633, prefix133, suffix133) + } else { + match133 = this.stringIndexMatch(data, prefix133, suffix133) + } + + return match133 !== undefined ? match133 : match633 } } diff --git a/src/integrations/terminal/TerminalRegistry.ts b/src/integrations/terminal/TerminalRegistry.ts index 2fb49e4825..69a21d94fd 100644 --- a/src/integrations/terminal/TerminalRegistry.ts +++ b/src/integrations/terminal/TerminalRegistry.ts @@ -5,6 +5,9 @@ export interface TerminalInfo { busy: boolean lastCommand: string id: number + stream?: AsyncIterable + running: boolean + streamClosed: boolean } // Although vscode.window.terminals provides a list of all open terminals, there's no way to know whether they're busy or not (exitStatus does not provide useful information for most commands). In order to prevent creating too many terminals, we need to keep track of terminals through the life of the extension, as well as session specific terminals for the life of a task (to get latest unretrieved output). @@ -20,34 +23,61 @@ export class TerminalRegistry { iconPath: new vscode.ThemeIcon("rocket"), env: { PAGER: "cat", + + // VSCode bug#237208: Command output can be lost due to a race between completion + // sequences and consumers. Add 50ms delay via PROMPT_COMMAND to ensure the + // \x1b]633;D escape sequence arrives after command output is processed. + PROMPT_COMMAND: "sleep 0.050", + + // VTE must be disabled because it prevents the prompt command above from executing + // See https://wiki.gnome.org/Apps/Terminal/VTE + VTE_VERSION: "0", }, }) + const newInfo: TerminalInfo = { terminal, busy: false, lastCommand: "", id: this.nextTerminalId++, + running: false, + streamClosed: false, } + this.terminals.push(newInfo) return newInfo } static getTerminal(id: number): TerminalInfo | undefined { const terminalInfo = this.terminals.find((t) => t.id === id) + if (terminalInfo && this.isTerminalClosed(terminalInfo.terminal)) { this.removeTerminal(id) return undefined } + return terminalInfo } static updateTerminal(id: number, updates: Partial) { const terminal = this.getTerminal(id) + if (terminal) { Object.assign(terminal, updates) } } + static getTerminalInfoByTerminal(terminal: vscode.Terminal): TerminalInfo | undefined { + const terminalInfo = this.terminals.find((t) => t.terminal === terminal) + + if (terminalInfo && this.isTerminalClosed(terminalInfo.terminal)) { + this.removeTerminal(terminalInfo.id) + return undefined + } + + return terminalInfo + } + static removeTerminal(id: number) { this.terminals = this.terminals.filter((t) => t.id !== id) } diff --git a/src/integrations/terminal/__tests__/TerminalProcess.test.ts b/src/integrations/terminal/__tests__/TerminalProcess.test.ts index 9ccbaef920..11c0339f27 100644 --- a/src/integrations/terminal/__tests__/TerminalProcess.test.ts +++ b/src/integrations/terminal/__tests__/TerminalProcess.test.ts @@ -1,9 +1,24 @@ -import { TerminalProcess, mergePromise } from "../TerminalProcess" -import * as vscode from "vscode" -import { EventEmitter } from "events" +// npx jest src/integrations/terminal/__tests__/TerminalProcess.test.ts -// Mock vscode -jest.mock("vscode") +import * as vscode from "vscode" + +import { TerminalProcess, mergePromise } from "../TerminalProcess" +import { TerminalInfo, TerminalRegistry } from "../TerminalRegistry" + +// Mock vscode.window.createTerminal +const mockCreateTerminal = jest.fn() + +jest.mock("vscode", () => ({ + window: { + createTerminal: (...args: any[]) => { + mockCreateTerminal(...args) + return { + exitStatus: undefined, + } + }, + }, + ThemeIcon: jest.fn(), +})) describe("TerminalProcess", () => { let terminalProcess: TerminalProcess @@ -14,6 +29,7 @@ describe("TerminalProcess", () => { } } > + let mockTerminalInfo: TerminalInfo let mockExecution: any let mockStream: AsyncIterableIterator @@ -25,7 +41,7 @@ describe("TerminalProcess", () => { shellIntegration: { executeCommand: jest.fn(), }, - name: "Mock Terminal", + name: "Roo Code", processId: Promise.resolve(123), creationOptions: {}, exitStatus: undefined, @@ -42,27 +58,39 @@ describe("TerminalProcess", () => { } > + mockTerminalInfo = { + terminal: mockTerminal, + busy: false, + lastCommand: "", + id: 1, + running: false, + streamClosed: false, + } + + TerminalRegistry["terminals"].push(mockTerminalInfo) + // Reset event listeners terminalProcess.removeAllListeners() }) describe("run", () => { it("handles shell integration commands correctly", async () => { - const lines: string[] = [] - terminalProcess.on("line", (line) => { - // Skip empty lines used for loading spinner - if (line !== "") { - lines.push(line) + let lines: string[] = [] + + terminalProcess.on("completed", (output) => { + if (output) { + lines = output.split("\n") } }) - // Mock stream data with shell integration sequences + // Mock stream data with shell integration sequences. mockStream = (async function* () { - // The first chunk contains the command start sequence + yield "\x1b]633;C\x07" // The first chunk contains the command start sequence with bell character. yield "Initial output\n" yield "More output\n" - // The last chunk contains the command end sequence yield "Final output" + yield "\x1b]633;D\x07" // The last chunk contains the command end sequence with bell character. + terminalProcess.emit("shell_execution_complete", mockTerminalInfo.id, { exitCode: 0 }) })() mockExecution = { @@ -71,12 +99,9 @@ describe("TerminalProcess", () => { mockTerminal.shellIntegration.executeCommand.mockReturnValue(mockExecution) - const completedPromise = new Promise((resolve) => { - terminalProcess.once("completed", resolve) - }) - - await terminalProcess.run(mockTerminal, "test command") - await completedPromise + const runPromise = terminalProcess.run(mockTerminal, "test command") + terminalProcess.emit("stream_available", mockTerminalInfo.id, mockStream) + await runPromise expect(lines).toEqual(["Initial output", "More output", "Final output"]) expect(terminalProcess.isHot).toBe(false) @@ -99,95 +124,41 @@ describe("TerminalProcess", () => { }) it("sets hot state for compiling commands", async () => { - const lines: string[] = [] - terminalProcess.on("line", (line) => { - if (line !== "") { - lines.push(line) + let lines: string[] = [] + + terminalProcess.on("completed", (output) => { + if (output) { + lines = output.split("\n") } }) - // Create a promise that resolves when the first chunk is processed - const firstChunkProcessed = new Promise((resolve) => { - terminalProcess.on("line", () => resolve()) + const completePromise = new Promise((resolve) => { + terminalProcess.on("shell_execution_complete", () => resolve()) }) mockStream = (async function* () { + yield "\x1b]633;C\x07" // The first chunk contains the command start sequence with bell character. yield "compiling...\n" - // Wait to ensure hot state check happens after first chunk - await new Promise((resolve) => setTimeout(resolve, 10)) yield "still compiling...\n" yield "done" + yield "\x1b]633;D\x07" // The last chunk contains the command end sequence with bell character. + terminalProcess.emit("shell_execution_complete", mockTerminalInfo.id, { exitCode: 0 }) })() - mockExecution = { + mockTerminal.shellIntegration.executeCommand.mockReturnValue({ read: jest.fn().mockReturnValue(mockStream), - } - - mockTerminal.shellIntegration.executeCommand.mockReturnValue(mockExecution) - - // Start the command execution - const runPromise = terminalProcess.run(mockTerminal, "npm run build") - - // Wait for the first chunk to be processed - await firstChunkProcessed - - // Hot state should be true while compiling - expect(terminalProcess.isHot).toBe(true) - - // Complete the execution - const completedPromise = new Promise((resolve) => { - terminalProcess.once("completed", resolve) }) + const runPromise = terminalProcess.run(mockTerminal, "npm run build") + terminalProcess.emit("stream_available", mockTerminalInfo.id, mockStream) + + expect(terminalProcess.isHot).toBe(true) await runPromise - await completedPromise expect(lines).toEqual(["compiling...", "still compiling...", "done"]) - }) - }) - describe("buffer processing", () => { - it("correctly processes and emits lines", () => { - const lines: string[] = [] - terminalProcess.on("line", (line) => lines.push(line)) - - // Simulate incoming chunks - terminalProcess["emitIfEol"]("first line\n") - terminalProcess["emitIfEol"]("second") - terminalProcess["emitIfEol"](" line\n") - terminalProcess["emitIfEol"]("third line") - - expect(lines).toEqual(["first line", "second line"]) - - // Process remaining buffer - terminalProcess["emitRemainingBufferIfListening"]() - expect(lines).toEqual(["first line", "second line", "third line"]) - }) - - it("handles Windows-style line endings", () => { - const lines: string[] = [] - terminalProcess.on("line", (line) => lines.push(line)) - - terminalProcess["emitIfEol"]("line1\r\nline2\r\n") - - expect(lines).toEqual(["line1", "line2"]) - }) - }) - - describe("removeLastLineArtifacts", () => { - it("removes terminal artifacts from output", () => { - const cases = [ - ["output%", "output"], - ["output$ ", "output"], - ["output#", "output"], - ["output> ", "output"], - ["multi\nline%", "multi\nline"], - ["no artifacts", "no artifacts"], - ] - - for (const [input, expected] of cases) { - expect(terminalProcess["removeLastLineArtifacts"](input)).toBe(expected) - } + await completePromise + expect(terminalProcess.isHot).toBe(false) }) }) @@ -204,14 +175,14 @@ describe("TerminalProcess", () => { }) describe("getUnretrievedOutput", () => { - it("returns and clears unretrieved output", () => { - terminalProcess["fullOutput"] = "previous\nnew output" - terminalProcess["lastRetrievedIndex"] = 9 // After "previous\n" + it.only("returns and clears unretrieved output", () => { + terminalProcess["fullOutput"] = `\x1b]633;C\x07previous\nnew output\x1b]633;D\x07` + terminalProcess["lastRetrievedIndex"] = 17 // After "previous\n" const unretrieved = terminalProcess.getUnretrievedOutput() - expect(unretrieved).toBe("new output") - expect(terminalProcess["lastRetrievedIndex"]).toBe(terminalProcess["fullOutput"].length) + + expect(terminalProcess["lastRetrievedIndex"]).toBe(terminalProcess["fullOutput"].length - "previous".length) }) }) diff --git a/src/integrations/terminal/__tests__/TerminalRegistry.test.ts b/src/integrations/terminal/__tests__/TerminalRegistry.test.ts index cc667a851b..a2b8fcd3b0 100644 --- a/src/integrations/terminal/__tests__/TerminalRegistry.test.ts +++ b/src/integrations/terminal/__tests__/TerminalRegistry.test.ts @@ -1,4 +1,5 @@ -import * as vscode from "vscode" +// npx jest src/integrations/terminal/__tests__/TerminalRegistry.test.ts + import { TerminalRegistry } from "../TerminalRegistry" // Mock vscode.window.createTerminal @@ -30,6 +31,8 @@ describe("TerminalRegistry", () => { iconPath: expect.any(Object), env: { PAGER: "cat", + PROMPT_COMMAND: "sleep 0.050", + VTE_VERSION: "0", }, }) }) diff --git a/src/shared/combineCommandSequences.ts b/src/shared/combineCommandSequences.ts index 31fe219f04..cbd674fc07 100644 --- a/src/shared/combineCommandSequences.ts +++ b/src/shared/combineCommandSequences.ts @@ -44,7 +44,7 @@ export function combineCommandSequences(messages: ClineMessage[]): ClineMessage[ // handle cases where we receive empty command_output (ie when extension is relinquishing control over exit command button) const output = messages[j].text || "" if (output.length > 0) { - combinedText += "\n" + output + combinedText += output } } j++ From e49d886d63965063b4392f8c36f1c052d7ac134c Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 28 Feb 2025 10:21:52 -0800 Subject: [PATCH 07/40] Re-enable tests --- src/integrations/terminal/__tests__/TerminalProcess.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrations/terminal/__tests__/TerminalProcess.test.ts b/src/integrations/terminal/__tests__/TerminalProcess.test.ts index 11c0339f27..44cae92580 100644 --- a/src/integrations/terminal/__tests__/TerminalProcess.test.ts +++ b/src/integrations/terminal/__tests__/TerminalProcess.test.ts @@ -175,7 +175,7 @@ describe("TerminalProcess", () => { }) describe("getUnretrievedOutput", () => { - it.only("returns and clears unretrieved output", () => { + it("returns and clears unretrieved output", () => { terminalProcess["fullOutput"] = `\x1b]633;C\x07previous\nnew output\x1b]633;D\x07` terminalProcess["lastRetrievedIndex"] = 17 // After "previous\n" From 4570ead71fb0ffead4af6ae3467831f0ed01f2de Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 28 Feb 2025 17:17:19 -0500 Subject: [PATCH 08/40] Update ChatView.tsx --- webview-ui/src/components/chat/ChatView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index fcd1ba9a3b..786f1ea957 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -880,7 +880,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie const placeholderText = useMemo(() => { const baseText = task ? "Type a message..." : "Type your task here..." const contextText = "(@ to add context, / to switch modes" - const imageText = shouldDisableImages ? "hold shift to drag in files" : ", hold shift to drag in files/images" + const imageText = shouldDisableImages ? ", hold shift to drag in files" : ", hold shift to drag in files/images" return baseText + `\n${contextText}${imageText})` }, [task, shouldDisableImages]) From 4caa49228fcfdac74872cb6d0486e380d9c984e6 Mon Sep 17 00:00:00 2001 From: ashktn Date: Fri, 28 Feb 2025 18:54:34 -0500 Subject: [PATCH 09/40] feat: Add support for Gemini models on Vertex AI --- .changeset/dry-suits-shake.md | 5 + package-lock.json | 13 + package.json | 1 + src/api/providers/__tests__/vertex.test.ts | 334 +++++++++++++++-- src/api/providers/vertex.ts | 128 ++++++- .../__tests__/vertex-gemini-format.test.ts | 338 ++++++++++++++++++ src/api/transform/vertex-gemini-format.ts | 83 +++++ src/shared/api.ts | 40 +++ 8 files changed, 897 insertions(+), 45 deletions(-) create mode 100644 .changeset/dry-suits-shake.md create mode 100644 src/api/transform/__tests__/vertex-gemini-format.test.ts create mode 100644 src/api/transform/vertex-gemini-format.ts diff --git a/.changeset/dry-suits-shake.md b/.changeset/dry-suits-shake.md new file mode 100644 index 0000000000..95bb8d3db4 --- /dev/null +++ b/.changeset/dry-suits-shake.md @@ -0,0 +1,5 @@ +--- +"roo-cline": minor +--- + +Add Gemini models on Vertex AI diff --git a/package-lock.json b/package-lock.json index 950769b39b..17cad607a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@anthropic-ai/sdk": "^0.37.0", "@anthropic-ai/vertex-sdk": "^0.7.0", "@aws-sdk/client-bedrock-runtime": "^3.706.0", + "@google-cloud/vertexai": "^1.9.3", "@google/generative-ai": "^0.18.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.0.1", @@ -3241,6 +3242,18 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@google-cloud/vertexai": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@google-cloud/vertexai/-/vertexai-1.9.3.tgz", + "integrity": "sha512-35o5tIEMLW3JeFJOaaMNR2e5sq+6rpnhrF97PuAxeOm0GlqVTESKhkGj7a5B5mmJSSSU3hUfIhcQCRRsw4Ipzg==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@google/generative-ai": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.18.0.tgz", diff --git a/package.json b/package.json index fe64af16d6..8e8bb0712a 100644 --- a/package.json +++ b/package.json @@ -308,6 +308,7 @@ "@anthropic-ai/vertex-sdk": "^0.7.0", "@aws-sdk/client-bedrock-runtime": "^3.706.0", "@google/generative-ai": "^0.18.0", + "@google-cloud/vertexai": "^1.9.3", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.0.1", "@types/clone-deep": "^4.0.4", diff --git a/src/api/providers/__tests__/vertex.test.ts b/src/api/providers/__tests__/vertex.test.ts index 9cf92f0a16..d3f34fcfa1 100644 --- a/src/api/providers/__tests__/vertex.test.ts +++ b/src/api/providers/__tests__/vertex.test.ts @@ -6,6 +6,7 @@ import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta" import { VertexHandler } from "../vertex" import { ApiStreamChunk } from "../../transform/stream" +import { VertexAI } from "@google-cloud/vertexai" // Mock Vertex SDK jest.mock("@anthropic-ai/vertex-sdk", () => ({ @@ -49,24 +50,100 @@ jest.mock("@anthropic-ai/vertex-sdk", () => ({ })), })) +// Mock Vertex Gemini SDK +jest.mock("@google-cloud/vertexai", () => { + const mockGenerateContentStream = jest.fn().mockImplementation(() => { + return { + stream: { + async *[Symbol.asyncIterator]() { + yield { + candidates: [ + { + content: { + parts: [{ text: "Test Gemini response" }], + }, + }, + ], + } + }, + }, + response: { + usageMetadata: { + promptTokenCount: 5, + candidatesTokenCount: 10, + }, + }, + } + }) + + const mockGenerateContent = jest.fn().mockResolvedValue({ + response: { + candidates: [ + { + content: { + parts: [{ text: "Test Gemini response" }], + }, + }, + ], + }, + }) + + const mockGenerativeModel = jest.fn().mockImplementation(() => { + return { + generateContentStream: mockGenerateContentStream, + generateContent: mockGenerateContent, + } + }) + + return { + VertexAI: jest.fn().mockImplementation(() => { + return { + getGenerativeModel: mockGenerativeModel, + } + }), + GenerativeModel: mockGenerativeModel, + } +}) + describe("VertexHandler", () => { let handler: VertexHandler - beforeEach(() => { - handler = new VertexHandler({ - apiModelId: "claude-3-5-sonnet-v2@20241022", - vertexProjectId: "test-project", - vertexRegion: "us-central1", - }) - }) - describe("constructor", () => { - it("should initialize with provided config", () => { + it("should initialize with provided config for Claude", () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + expect(AnthropicVertex).toHaveBeenCalledWith({ projectId: "test-project", region: "us-central1", }) }) + + it("should initialize with provided config for Gemini", () => { + handler = new VertexHandler({ + apiModelId: "gemini-1.5-pro-001", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + expect(VertexAI).toHaveBeenCalledWith({ + project: "test-project", + location: "us-central1", + }) + }) + + it("should throw error for invalid model", () => { + expect(() => { + new VertexHandler({ + apiModelId: "invalid-model", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + }).toThrow("Unknown model ID: invalid-model") + }) }) describe("createMessage", () => { @@ -83,7 +160,13 @@ describe("VertexHandler", () => { const systemPrompt = "You are a helpful assistant" - it("should handle streaming responses correctly", async () => { + it("should handle streaming responses correctly for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "message_start", @@ -127,7 +210,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) const chunks: ApiStreamChunk[] = [] @@ -187,7 +270,58 @@ describe("VertexHandler", () => { }) }) - it("should handle multiple content blocks with line breaks", async () => { + it("should handle streaming responses correctly for Gemini", async () => { + const mockGemini = require("@google-cloud/vertexai") + const mockGenerateContentStream = mockGemini.VertexAI().getGenerativeModel().generateContentStream + handler = new VertexHandler({ + apiModelId: "gemini-1.5-pro-001", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + const stream = handler.createMessage(systemPrompt, mockMessages) + const chunks: ApiStreamChunk[] = [] + + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(chunks.length).toBe(2) + expect(chunks[0]).toEqual({ + type: "text", + text: "Test Gemini response", + }) + expect(chunks[1]).toEqual({ + type: "usage", + inputTokens: 5, + outputTokens: 10, + }) + + expect(mockGenerateContentStream).toHaveBeenCalledWith({ + contents: [ + { + role: "user", + parts: [{ text: "Hello" }], + }, + { + role: "model", + parts: [{ text: "Hi there!" }], + }, + ], + generationConfig: { + maxOutputTokens: 16384, + temperature: 0, + }, + }) + }) + + it("should handle multiple content blocks with line breaks for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "content_block_start", @@ -216,7 +350,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) const chunks: ApiStreamChunk[] = [] @@ -240,10 +374,16 @@ describe("VertexHandler", () => { }) }) - it("should handle API errors", async () => { + it("should handle API errors for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockError = new Error("Vertex API error") const mockCreate = jest.fn().mockRejectedValue(mockError) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) @@ -254,7 +394,13 @@ describe("VertexHandler", () => { }).rejects.toThrow("Vertex API error") }) - it("should handle prompt caching for supported models", async () => { + it("should handle prompt caching for supported models for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "message_start", @@ -299,7 +445,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, [ { @@ -383,7 +529,13 @@ describe("VertexHandler", () => { ) }) - it("should handle cache-related usage metrics", async () => { + it("should handle cache-related usage metrics for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "message_start", @@ -415,7 +567,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) const chunks: ApiStreamChunk[] = [] @@ -442,7 +594,13 @@ describe("VertexHandler", () => { const systemPrompt = "You are a helpful assistant" - it("should handle thinking content blocks and deltas", async () => { + it("should handle thinking content blocks and deltas for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "message_start", @@ -488,7 +646,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) const chunks: ApiStreamChunk[] = [] @@ -510,7 +668,13 @@ describe("VertexHandler", () => { expect(textChunks[1].text).toBe("Here's my answer:") }) - it("should handle multiple thinking blocks with line breaks", async () => { + it("should handle multiple thinking blocks with line breaks for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockStream = [ { type: "content_block_start", @@ -539,7 +703,7 @@ describe("VertexHandler", () => { } const mockCreate = jest.fn().mockResolvedValue(asyncIterator) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const stream = handler.createMessage(systemPrompt, mockMessages) const chunks: ApiStreamChunk[] = [] @@ -565,10 +729,16 @@ describe("VertexHandler", () => { }) describe("completePrompt", () => { - it("should complete prompt successfully", async () => { + it("should complete prompt successfully for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const result = await handler.completePrompt("Test prompt") expect(result).toBe("Test response") - expect(handler["client"].messages.create).toHaveBeenCalledWith({ + expect(handler["anthropicClient"].messages.create).toHaveBeenCalledWith({ model: "claude-3-5-sonnet-v2@20241022", max_tokens: 8192, temperature: 0, @@ -583,31 +753,109 @@ describe("VertexHandler", () => { }) }) - it("should handle API errors", async () => { + it("should complete prompt successfully for Gemini", async () => { + const mockGemini = require("@google-cloud/vertexai") + const mockGenerateContent = mockGemini.VertexAI().getGenerativeModel().generateContent + + handler = new VertexHandler({ + apiModelId: "gemini-1.5-pro-001", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("Test Gemini response") + expect(mockGenerateContent).toHaveBeenCalled() + expect(mockGenerateContent).toHaveBeenCalledWith({ + contents: [{ role: "user", parts: [{ text: "Test prompt" }] }], + generationConfig: { + temperature: 0, + }, + }) + }) + + it("should handle API errors for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockError = new Error("Vertex API error") const mockCreate = jest.fn().mockRejectedValue(mockError) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate await expect(handler.completePrompt("Test prompt")).rejects.toThrow( "Vertex completion error: Vertex API error", ) }) - it("should handle non-text content", async () => { + it("should handle API errors for Gemini", async () => { + const mockGemini = require("@google-cloud/vertexai") + const mockGenerateContent = mockGemini.VertexAI().getGenerativeModel().generateContent + mockGenerateContent.mockRejectedValue(new Error("Vertex API error")) + handler = new VertexHandler({ + apiModelId: "gemini-1.5-pro-001", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + + await expect(handler.completePrompt("Test prompt")).rejects.toThrow( + "Vertex completion error: Vertex API error", + ) + }) + + it("should handle non-text content for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockCreate = jest.fn().mockResolvedValue({ content: [{ type: "image" }], }) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate const result = await handler.completePrompt("Test prompt") expect(result).toBe("") }) - it("should handle empty response", async () => { + it("should handle empty response for Claude", async () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const mockCreate = jest.fn().mockResolvedValue({ content: [{ type: "text", text: "" }], }) - ;(handler["client"].messages as any).create = mockCreate + ;(handler["anthropicClient"].messages as any).create = mockCreate + + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("") + }) + + it("should handle empty response for Gemini", async () => { + const mockGemini = require("@google-cloud/vertexai") + const mockGenerateContent = mockGemini.VertexAI().getGenerativeModel().generateContent + mockGenerateContent.mockResolvedValue({ + response: { + candidates: [ + { + content: { + parts: [{ text: "" }], + }, + }, + ], + }, + }) + handler = new VertexHandler({ + apiModelId: "gemini-1.5-pro-001", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) const result = await handler.completePrompt("Test prompt") expect(result).toBe("") @@ -615,7 +863,13 @@ describe("VertexHandler", () => { }) describe("getModel", () => { - it("should return correct model info", () => { + it("should return correct model info for Claude", () => { + handler = new VertexHandler({ + apiModelId: "claude-3-5-sonnet-v2@20241022", + vertexProjectId: "test-project", + vertexRegion: "us-central1", + }) + const modelInfo = handler.getModel() expect(modelInfo.id).toBe("claude-3-5-sonnet-v2@20241022") expect(modelInfo.info).toBeDefined() @@ -623,14 +877,18 @@ describe("VertexHandler", () => { expect(modelInfo.info.contextWindow).toBe(200_000) }) - it("should return default model if invalid model specified", () => { - const invalidHandler = new VertexHandler({ - apiModelId: "invalid-model", + it("should return correct model info for Gemini", () => { + handler = new VertexHandler({ + apiModelId: "gemini-2.0-flash-001", vertexProjectId: "test-project", vertexRegion: "us-central1", }) - const modelInfo = invalidHandler.getModel() - expect(modelInfo.id).toBe("claude-3-7-sonnet@20250219") // Default model + + const modelInfo = handler.getModel() + expect(modelInfo.id).toBe("gemini-2.0-flash-001") + expect(modelInfo.info).toBeDefined() + expect(modelInfo.info.maxTokens).toBe(8192) + expect(modelInfo.info.contextWindow).toBe(1048576) }) }) @@ -724,7 +982,7 @@ describe("VertexHandler", () => { }, } }) - ;(thinkingHandler["client"].messages as any).create = mockCreate + ;(thinkingHandler["anthropicClient"].messages as any).create = mockCreate await thinkingHandler .createMessage("You are a helpful assistant", [{ role: "user", content: "Hello" }]) diff --git a/src/api/providers/vertex.ts b/src/api/providers/vertex.ts index a25fad07ee..5b8fc1ad78 100644 --- a/src/api/providers/vertex.ts +++ b/src/api/providers/vertex.ts @@ -5,6 +5,8 @@ import { ApiHandler, SingleCompletionHandler } from "../" import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta" import { ApiHandlerOptions, ModelInfo, vertexDefaultModelId, VertexModelId, vertexModels } from "../../shared/api" import { ApiStream } from "../transform/stream" +import { VertexAI } from "@google-cloud/vertexai" +import { convertAnthropicMessageToVertexGemini } from "../transform/vertex-gemini-format" // Types for Vertex SDK @@ -91,19 +93,37 @@ interface VertexMessageStreamEvent { thinking: string } } - // https://docs.anthropic.com/en/api/claude-on-vertex-ai export class VertexHandler implements ApiHandler, SingleCompletionHandler { + MODEL_CLAUDE = "claude" + MODEL_GEMINI = "gemini" + private options: ApiHandlerOptions - private client: AnthropicVertex + private anthropicClient: AnthropicVertex + private geminiClient: VertexAI + private modelType: string constructor(options: ApiHandlerOptions) { this.options = options - this.client = new AnthropicVertex({ + + if (this.options.apiModelId?.startsWith(this.MODEL_CLAUDE)) { + this.modelType = this.MODEL_CLAUDE + } else if (this.options.apiModelId?.startsWith(this.MODEL_GEMINI)) { + this.modelType = this.MODEL_GEMINI + } else { + throw new Error(`Unknown model ID: ${this.options.apiModelId}`) + } + + this.anthropicClient = new AnthropicVertex({ projectId: this.options.vertexProjectId ?? "not-provided", // https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#regions region: this.options.vertexRegion ?? "us-east5", }) + + this.geminiClient = new VertexAI({ + project: this.options.vertexProjectId ?? "not-provided", + location: this.options.vertexRegion ?? "us-east5", + }) } private formatMessageForCache(message: Anthropic.Messages.MessageParam, shouldCache: boolean): VertexMessage { @@ -154,7 +174,42 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { } } - async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + private async *createGeminiMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const model = this.geminiClient.getGenerativeModel({ + model: this.getModel().id, + systemInstruction: systemPrompt, + }) + + const result = await model.generateContentStream({ + contents: messages.map(convertAnthropicMessageToVertexGemini), + generationConfig: { + maxOutputTokens: this.getModel().info.maxTokens, + temperature: this.options.modelTemperature ?? 0, + }, + }) + + for await (const chunk of result.stream) { + if (chunk.candidates?.[0]?.content?.parts) { + for (const part of chunk.candidates[0].content.parts) { + if (part.text) { + yield { + type: "text", + text: part.text, + } + } + } + } + } + + const response = await result.response + yield { + type: "usage", + inputTokens: response.usageMetadata?.promptTokenCount ?? 0, + outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0, + } + } + + private async *createClaudeMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { const model = this.getModel() let { id, info, temperature, maxTokens, thinking } = model const useCache = model.info.supportsPromptCache @@ -192,7 +247,7 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { stream: true, } - const stream = (await this.client.messages.create( + const stream = (await this.anthropicClient.messages.create( params as Anthropic.Messages.MessageCreateParamsStreaming, )) as unknown as AnthropicStream @@ -272,6 +327,22 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { } } + async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + switch (this.modelType) { + case this.MODEL_CLAUDE: { + yield* this.createClaudeMessage(systemPrompt, messages) + break + } + case this.MODEL_GEMINI: { + yield* this.createGeminiMessage(systemPrompt, messages) + break + } + default: { + throw new Error(`Invalid model type: ${this.modelType}`) + } + } + } + getModel(): { id: VertexModelId info: ModelInfo @@ -316,7 +387,36 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { return { id, info, temperature, maxTokens, thinking } } - async completePrompt(prompt: string): Promise { + private async completePromptGemini(prompt: string): Promise { + try { + const model = this.geminiClient.getGenerativeModel({ + model: this.getModel().id, + }) + + const result = await model.generateContent({ + contents: [{ role: "user", parts: [{ text: prompt }] }], + generationConfig: { + temperature: this.options.modelTemperature ?? 0, + }, + }) + + let text = "" + result.response.candidates?.forEach((candidate) => { + candidate.content.parts.forEach((part) => { + text += part.text + }) + }) + + return text + } catch (error) { + if (error instanceof Error) { + throw new Error(`Vertex completion error: ${error.message}`) + } + throw error + } + } + + private async completePromptClaude(prompt: string): Promise { try { let { id, info, temperature, maxTokens, thinking } = this.getModel() const useCache = info.supportsPromptCache @@ -344,7 +444,7 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { stream: false, } - const response = (await this.client.messages.create( + const response = (await this.anthropicClient.messages.create( params as Anthropic.Messages.MessageCreateParamsNonStreaming, )) as unknown as VertexMessageResponse @@ -360,4 +460,18 @@ export class VertexHandler implements ApiHandler, SingleCompletionHandler { throw error } } + + async completePrompt(prompt: string): Promise { + switch (this.modelType) { + case this.MODEL_CLAUDE: { + return this.completePromptClaude(prompt) + } + case this.MODEL_GEMINI: { + return this.completePromptGemini(prompt) + } + default: { + throw new Error(`Invalid model type: ${this.modelType}`) + } + } + } } diff --git a/src/api/transform/__tests__/vertex-gemini-format.test.ts b/src/api/transform/__tests__/vertex-gemini-format.test.ts new file mode 100644 index 0000000000..bcb26df099 --- /dev/null +++ b/src/api/transform/__tests__/vertex-gemini-format.test.ts @@ -0,0 +1,338 @@ +// npx jest src/api/transform/__tests__/vertex-gemini-format.test.ts + +import { Anthropic } from "@anthropic-ai/sdk" + +import { convertAnthropicMessageToVertexGemini } from "../vertex-gemini-format" + +describe("convertAnthropicMessageToVertexGemini", () => { + it("should convert a simple text message", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: "Hello, world!", + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [{ text: "Hello, world!" }], + }) + }) + + it("should convert assistant role to model role", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "assistant", + content: "I'm an assistant", + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "model", + parts: [{ text: "I'm an assistant" }], + }) + }) + + it("should convert a message with text blocks", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { type: "text", text: "First paragraph" }, + { type: "text", text: "Second paragraph" }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [{ text: "First paragraph" }, { text: "Second paragraph" }], + }) + }) + + it("should convert a message with an image", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { type: "text", text: "Check out this image:" }, + { + type: "image", + source: { + type: "base64", + media_type: "image/jpeg", + data: "base64encodeddata", + }, + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [ + { text: "Check out this image:" }, + { + inlineData: { + data: "base64encodeddata", + mimeType: "image/jpeg", + }, + }, + ], + }) + }) + + it("should throw an error for unsupported image source type", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "image", + source: { + type: "url", // Not supported + url: "https://example.com/image.jpg", + } as any, + }, + ], + } + + expect(() => convertAnthropicMessageToVertexGemini(anthropicMessage)).toThrow("Unsupported image source type") + }) + + it("should convert a message with tool use", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "assistant", + content: [ + { type: "text", text: "Let me calculate that for you." }, + { + type: "tool_use", + id: "calc-123", + name: "calculator", + input: { operation: "add", numbers: [2, 3] }, + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "model", + parts: [ + { text: "Let me calculate that for you." }, + { + functionCall: { + name: "calculator", + args: { operation: "add", numbers: [2, 3] }, + }, + }, + ], + }) + }) + + it("should convert a message with tool result as string", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { type: "text", text: "Here's the result:" }, + { + type: "tool_result", + tool_use_id: "calculator-123", + content: "The result is 5", + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [ + { text: "Here's the result:" }, + { + functionResponse: { + name: "calculator", + response: { + name: "calculator", + content: "The result is 5", + }, + }, + }, + ], + }) + }) + + it("should handle empty tool result content", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "calculator-123", + content: null as any, // Empty content + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + // Should skip the empty tool result + expect(result).toEqual({ + role: "user", + parts: [], + }) + }) + + it("should convert a message with tool result as array with text only", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "search-123", + content: [ + { type: "text", text: "First result" }, + { type: "text", text: "Second result" }, + ], + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [ + { + functionResponse: { + name: "search", + response: { + name: "search", + content: "First result\n\nSecond result", + }, + }, + }, + ], + }) + }) + + it("should convert a message with tool result as array with text and images", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "search-123", + content: [ + { type: "text", text: "Search results:" }, + { + type: "image", + source: { + type: "base64", + media_type: "image/png", + data: "image1data", + }, + }, + { + type: "image", + source: { + type: "base64", + media_type: "image/jpeg", + data: "image2data", + }, + }, + ], + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [ + { + functionResponse: { + name: "search", + response: { + name: "search", + content: "Search results:\n\n(See next part for image)", + }, + }, + }, + { + inlineData: { + data: "image1data", + mimeType: "image/png", + }, + }, + { + inlineData: { + data: "image2data", + mimeType: "image/jpeg", + }, + }, + ], + }) + }) + + it("should convert a message with tool result containing only images", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: "imagesearch-123", + content: [ + { + type: "image", + source: { + type: "base64", + media_type: "image/png", + data: "onlyimagedata", + }, + }, + ], + }, + ], + } + + const result = convertAnthropicMessageToVertexGemini(anthropicMessage) + + expect(result).toEqual({ + role: "user", + parts: [ + { + functionResponse: { + name: "imagesearch", + response: { + name: "imagesearch", + content: "\n\n(See next part for image)", + }, + }, + }, + { + inlineData: { + data: "onlyimagedata", + mimeType: "image/png", + }, + }, + ], + }) + }) + + it("should throw an error for unsupported content block type", () => { + const anthropicMessage: Anthropic.Messages.MessageParam = { + role: "user", + content: [ + { + type: "unknown_type", // Unsupported type + data: "some data", + } as any, + ], + } + + expect(() => convertAnthropicMessageToVertexGemini(anthropicMessage)).toThrow( + "Unsupported content block type: unknown_type", + ) + }) +}) diff --git a/src/api/transform/vertex-gemini-format.ts b/src/api/transform/vertex-gemini-format.ts new file mode 100644 index 0000000000..75abb7d3be --- /dev/null +++ b/src/api/transform/vertex-gemini-format.ts @@ -0,0 +1,83 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import { Content, FunctionCallPart, FunctionResponsePart, InlineDataPart, Part, TextPart } from "@google-cloud/vertexai" + +function convertAnthropicContentToVertexGemini(content: Anthropic.Messages.MessageParam["content"]): Part[] { + if (typeof content === "string") { + return [{ text: content } as TextPart] + } + + return content.flatMap((block) => { + switch (block.type) { + case "text": + return { text: block.text } as TextPart + case "image": + if (block.source.type !== "base64") { + throw new Error("Unsupported image source type") + } + return { + inlineData: { + data: block.source.data, + mimeType: block.source.media_type, + }, + } as InlineDataPart + case "tool_use": + return { + functionCall: { + name: block.name, + args: block.input, + }, + } as FunctionCallPart + case "tool_result": + const name = block.tool_use_id.split("-")[0] + if (!block.content) { + return [] + } + if (typeof block.content === "string") { + return { + functionResponse: { + name, + response: { + name, + content: block.content, + }, + }, + } as FunctionResponsePart + } else { + // The only case when tool_result could be array is when the tool failed and we're providing ie user feedback potentially with images + const textParts = block.content.filter((part) => part.type === "text") + const imageParts = block.content.filter((part) => part.type === "image") + const text = textParts.length > 0 ? textParts.map((part) => part.text).join("\n\n") : "" + const imageText = imageParts.length > 0 ? "\n\n(See next part for image)" : "" + return [ + { + functionResponse: { + name, + response: { + name, + content: text + imageText, + }, + }, + } as FunctionResponsePart, + ...imageParts.map( + (part) => + ({ + inlineData: { + data: part.source.data, + mimeType: part.source.media_type, + }, + }) as InlineDataPart, + ), + ] + } + default: + throw new Error(`Unsupported content block type: ${(block as any).type}`) + } + }) +} + +export function convertAnthropicMessageToVertexGemini(message: Anthropic.Messages.MessageParam): Content { + return { + role: message.role === "assistant" ? "model" : "user", + parts: convertAnthropicContentToVertexGemini(message.content), + } +} diff --git a/src/shared/api.ts b/src/shared/api.ts index 99e2986e88..b16e5142a0 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -436,6 +436,46 @@ export const openRouterDefaultModelInfo: ModelInfo = { export type VertexModelId = keyof typeof vertexModels export const vertexDefaultModelId: VertexModelId = "claude-3-7-sonnet@20250219" export const vertexModels = { + "gemini-2.0-flash-001": { + maxTokens: 8192, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.15, + outputPrice: 0.6, + }, + "gemini-2.0-flash-lite-001": { + maxTokens: 8192, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.075, + outputPrice: 0.3, + }, + "gemini-2.0-flash-thinking-exp-01-21": { + maxTokens: 8192, + contextWindow: 32_768, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + }, + "gemini-1.5-flash-002": { + maxTokens: 8192, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.075, + outputPrice: 0.3, + }, + "gemini-1.5-pro-002": { + maxTokens: 8192, + contextWindow: 2_097_152, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 1.25, + outputPrice: 5, + }, "claude-3-7-sonnet@20250219:thinking": { maxTokens: 64_000, contextWindow: 200_000, From 1d3f5380a80d09ac747d93c701473215bdc03ba6 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 28 Feb 2025 21:23:11 -0800 Subject: [PATCH 10/40] Move integration tests into its own module --- .github/workflows/changeset-release.yml | 2 +- .github/workflows/code-qa.yml | 18 +- .vscodeignore | 3 +- .../.env.integration.example | 0 .vscode-test.mjs => e2e/.vscode-test.mjs | 2 +- {src/test => e2e}/VSCODE_INTEGRATION_TESTS.md | 4 +- e2e/package-lock.json | 2387 +++++++++++++++++ e2e/package.json | 21 + {src/test => e2e/src}/runTest.ts | 2 +- {src/test => e2e/src}/suite/extension.test.ts | 0 {src/test => e2e/src}/suite/index.ts | 3 +- {src/test => e2e/src}/suite/modes.test.ts | 0 {src/test => e2e/src}/suite/task.test.ts | 0 .../tsconfig.json | 7 +- knip.json | 4 +- package-lock.json | 922 +------ package.json | 33 +- src/exports/cline.d.ts | 93 + src/shared/modes.ts | 6 +- 19 files changed, 2548 insertions(+), 959 deletions(-) rename .env.integration.example => e2e/.env.integration.example (100%) rename .vscode-test.mjs => e2e/.vscode-test.mjs (89%) rename {src/test => e2e}/VSCODE_INTEGRATION_TESTS.md (98%) create mode 100644 e2e/package-lock.json create mode 100644 e2e/package.json rename {src/test => e2e/src}/runTest.ts (98%) rename {src/test => e2e/src}/suite/extension.test.ts (100%) rename {src/test => e2e/src}/suite/index.ts (95%) rename {src/test => e2e/src}/suite/modes.test.ts (100%) rename {src/test => e2e/src}/suite/task.test.ts (100%) rename tsconfig.integration.json => e2e/tsconfig.json (60%) diff --git a/.github/workflows/changeset-release.yml b/.github/workflows/changeset-release.yml index a2bcd3f039..462516365b 100644 --- a/.github/workflows/changeset-release.yml +++ b/.github/workflows/changeset-release.yml @@ -37,7 +37,7 @@ jobs: cache: 'npm' - name: Install Dependencies - run: npm run install:all + run: npm run install:ci # Check if there are any new changesets to process - name: Check for changesets diff --git a/.github/workflows/code-qa.yml b/.github/workflows/code-qa.yml index fde891f804..b7292dd9ee 100644 --- a/.github/workflows/code-qa.yml +++ b/.github/workflows/code-qa.yml @@ -20,7 +20,7 @@ jobs: node-version: '18' cache: 'npm' - name: Install dependencies - run: npm run install:all + run: npm run install:ci - name: Compile run: npm run compile - name: Check types @@ -39,7 +39,7 @@ jobs: node-version: '18' cache: 'npm' - name: Install dependencies - run: npm run install:all + run: npm run install:ci - name: Run knip checks run: npm run knip @@ -54,7 +54,7 @@ jobs: node-version: '18' cache: 'npm' - name: Install dependencies - run: npm run install:all + run: npm run install:ci - name: Run unit tests run: npx jest --silent @@ -69,7 +69,7 @@ jobs: node-version: '18' cache: 'npm' - name: Install dependencies - run: npm run install:all + run: npm run install:ci - name: Run unit tests working-directory: webview-ui run: npx jest --silent @@ -108,9 +108,11 @@ jobs: with: node-version: '18' cache: 'npm' - - name: Create env.integration file - run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.integration - name: Install dependencies - run: npm run install:all + run: npm run install:ci + - name: Create env.integration file + working-directory: e2e + run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.integration - name: Run integration tests - run: xvfb-run -a npm run test:integration + working-directory: e2e + run: xvfb-run -a npm run ci diff --git a/.vscodeignore b/.vscodeignore index 638ac22db7..1fc5a728b0 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -4,6 +4,8 @@ .vscode/** .vscode-test/** out/** +out-integration/** +e2e/** node_modules/** src/** .gitignore @@ -25,7 +27,6 @@ demo.gif .roomodes cline_docs/** coverage/** -out-integration/** # Ignore all webview-ui files except the build directory (https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore) webview-ui/src/** diff --git a/.env.integration.example b/e2e/.env.integration.example similarity index 100% rename from .env.integration.example rename to e2e/.env.integration.example diff --git a/.vscode-test.mjs b/e2e/.vscode-test.mjs similarity index 89% rename from .vscode-test.mjs rename to e2e/.vscode-test.mjs index dd7760789b..16ea4271ba 100644 --- a/.vscode-test.mjs +++ b/e2e/.vscode-test.mjs @@ -6,7 +6,7 @@ import { defineConfig } from '@vscode/test-cli'; export default defineConfig({ label: 'integrationTest', - files: 'out-integration/test/**/*.test.js', + files: 'out/e2e/src/suite/**/*.test.js', workspaceFolder: '.', mocha: { ui: 'tdd', diff --git a/src/test/VSCODE_INTEGRATION_TESTS.md b/e2e/VSCODE_INTEGRATION_TESTS.md similarity index 98% rename from src/test/VSCODE_INTEGRATION_TESTS.md rename to e2e/VSCODE_INTEGRATION_TESTS.md index f5882fea1e..25f54492de 100644 --- a/src/test/VSCODE_INTEGRATION_TESTS.md +++ b/e2e/VSCODE_INTEGRATION_TESTS.md @@ -11,8 +11,8 @@ The integration tests use the `@vscode/test-electron` package to run tests in a ### Directory Structure ``` -src/test/ -├── runTest.ts # Main test runner +e2e/src/ +├── runTest.ts # Main test runner ├── suite/ │ ├── index.ts # Test suite configuration │ ├── modes.test.ts # Mode switching tests diff --git a/e2e/package-lock.json b/e2e/package-lock.json new file mode 100644 index 0000000000..278df120c2 --- /dev/null +++ b/e2e/package-lock.json @@ -0,0 +1,2387 @@ +{ + "name": "e2e", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "e2e", + "version": "0.1.0", + "devDependencies": { + "@types/mocha": "^10.0.10", + "@vscode/test-cli": "^0.0.9", + "@vscode/test-electron": "^2.4.0", + "mocha": "^11.1.0", + "typescript": "^5.4.5" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/test-cli": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.9.tgz", + "integrity": "sha512-vsl5/ueE3Jf0f6XzB0ECHHMsd5A0Yu6StElb8a+XsubZW7kHNAOw4Y3TSSuDzKEpLnJ92nbMy1Zl+KLGCE6NaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mocha": "^10.0.2", + "c8": "^9.1.0", + "chokidar": "^3.5.3", + "enhanced-resolve": "^5.15.0", + "glob": "^10.3.10", + "minimatch": "^9.0.3", + "mocha": "^10.2.0", + "supports-color": "^9.4.0", + "yargs": "^17.7.2" + }, + "bin": { + "vscode-test": "out/bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vscode/test-cli/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@vscode/test-cli/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/@vscode/test-cli/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/test-cli/node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/test-cli/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@vscode/test-cli/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@vscode/test-cli/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@vscode/test-cli/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/test-electron": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", + "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "jszip": "^3.10.1", + "ora": "^7.0.1", + "semver": "^7.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/c8": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", + "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=14.14.0" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", + "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", + "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.3.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "string-width": "^6.1.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/string-width": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 0000000000..7e0daac064 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,21 @@ +{ + "name": "e2e", + "version": "0.1.0", + "private": true, + "scripts": { + "build": "cd .. && npm run build", + "compile": "tsc -p tsconfig.json", + "lint": "eslint src --ext ts", + "check-types": "tsc --noEmit", + "test": "npm run compile && npx dotenvx run -f .env.integration -- node ./out/e2e/src/runTest.js", + "ci": "npm run build && npm run test" + }, + "dependencies": {}, + "devDependencies": { + "@types/mocha": "^10.0.10", + "@vscode/test-cli": "^0.0.9", + "@vscode/test-electron": "^2.4.0", + "mocha": "^11.1.0", + "typescript": "^5.4.5" + } +} diff --git a/src/test/runTest.ts b/e2e/src/runTest.ts similarity index 98% rename from src/test/runTest.ts rename to e2e/src/runTest.ts index 2576c6072f..28545efd46 100644 --- a/src/test/runTest.ts +++ b/e2e/src/runTest.ts @@ -6,7 +6,7 @@ async function main() { try { // The folder containing the Extension Manifest package.json // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, "../../") + const extensionDevelopmentPath = path.resolve(__dirname, "../../../../") // The path to the extension test script // Passed to --extensionTestsPath diff --git a/src/test/suite/extension.test.ts b/e2e/src/suite/extension.test.ts similarity index 100% rename from src/test/suite/extension.test.ts rename to e2e/src/suite/extension.test.ts diff --git a/src/test/suite/index.ts b/e2e/src/suite/index.ts similarity index 95% rename from src/test/suite/index.ts rename to e2e/src/suite/index.ts index cc487b0bf7..a9540d9600 100644 --- a/src/test/suite/index.ts +++ b/e2e/src/suite/index.ts @@ -1,8 +1,7 @@ import * as path from "path" import Mocha from "mocha" import { glob } from "glob" -import { ClineAPI } from "../../exports/cline" -import { ClineProvider } from "../../core/webview/ClineProvider" +import { ClineAPI, ClineProvider } from "../../../src/exports/cline" import * as vscode from "vscode" declare global { diff --git a/src/test/suite/modes.test.ts b/e2e/src/suite/modes.test.ts similarity index 100% rename from src/test/suite/modes.test.ts rename to e2e/src/suite/modes.test.ts diff --git a/src/test/suite/task.test.ts b/e2e/src/suite/task.test.ts similarity index 100% rename from src/test/suite/task.test.ts rename to e2e/src/suite/task.test.ts diff --git a/tsconfig.integration.json b/e2e/tsconfig.json similarity index 60% rename from tsconfig.integration.json rename to e2e/tsconfig.json index 0de0ea736a..9958110767 100644 --- a/tsconfig.integration.json +++ b/e2e/tsconfig.json @@ -9,9 +9,8 @@ "strict": true, "skipLibCheck": true, "useUnknownInCatchVariables": false, - "rootDir": "src", - "outDir": "out-integration" + "outDir": "out" }, - "include": ["**/*.ts"], - "exclude": [".vscode-test", "benchmark", "dist", "**/node_modules/**", "out", "out-integration", "webview-ui"] + "include": ["src", "../src/exports"], + "exclude": [".vscode-test", "**/node_modules/**", "out"] } diff --git a/knip.json b/knip.json index b0e0839da7..a9f0b93e0d 100644 --- a/knip.json +++ b/knip.json @@ -16,7 +16,9 @@ "src/activate/**", "src/exports/**", "src/extension.ts", - ".vscode-test.mjs" + "e2e/.vscode-test.mjs", + "e2e/src/runTest.ts", + "e2e/src/suite/index.ts" ], "workspaces": { "webview-ui": { diff --git a/package-lock.json b/package-lock.json index 950769b39b..a8fa18b5f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,13 +63,10 @@ "@types/diff-match-patch": "^1.0.36", "@types/glob": "^8.1.0", "@types/jest": "^29.5.14", - "@types/mocha": "^10.0.10", "@types/node": "20.x", "@types/string-similarity": "^4.0.2", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.11.0", - "@vscode/test-cli": "^0.0.9", - "@vscode/test-electron": "^2.4.0", "esbuild": "^0.24.0", "eslint": "^8.57.0", "glob": "^11.0.1", @@ -79,7 +76,6 @@ "knip": "^5.44.4", "lint-staged": "^15.2.11", "mkdirp": "^3.0.1", - "mocha": "^11.1.0", "npm-run-all": "^4.1.5", "prettier": "^3.4.2", "rimraf": "^6.0.1", @@ -4153,17 +4149,6 @@ "node": ">= 8" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@puppeteer/browsers": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", @@ -6032,13 +6017,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -6351,332 +6329,6 @@ "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.36.tgz", "integrity": "sha512-wsNOvNMMJ2BY8rC2N2MNBG7yOowV3ov8KlvUE/AiVUlHKTfWsw3OgAOQduX7h0Un6GssKD3aoTVH+TF3DSQwKQ==" }, - "node_modules/@vscode/test-cli": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.9.tgz", - "integrity": "sha512-vsl5/ueE3Jf0f6XzB0ECHHMsd5A0Yu6StElb8a+XsubZW7kHNAOw4Y3TSSuDzKEpLnJ92nbMy1Zl+KLGCE6NaA==", - "dev": true, - "dependencies": { - "@types/mocha": "^10.0.2", - "c8": "^9.1.0", - "chokidar": "^3.5.3", - "enhanced-resolve": "^5.15.0", - "glob": "^10.3.10", - "minimatch": "^9.0.3", - "mocha": "^10.2.0", - "supports-color": "^9.4.0", - "yargs": "^17.7.2" - }, - "bin": { - "vscode-test": "out/bin.mjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@vscode/test-cli/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@vscode/test-cli/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@vscode/test-cli/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/@vscode/test-cli/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vscode/test-cli/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/test-cli/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/@vscode/test-cli/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vscode/test-cli/node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/@vscode/test-cli/node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vscode/test-cli/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/test-cli/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@vscode/test-cli/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@vscode/test-cli/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@vscode/test-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@vscode/test-cli/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@vscode/test-electron": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", - "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", - "dev": true, - "dependencies": { - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "jszip": "^3.10.1", - "ora": "^7.0.1", - "semver": "^7.6.2" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", @@ -7179,43 +6831,6 @@ "node": "*" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dev": true, - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bluebird": { "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", @@ -7251,12 +6866,6 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, "node_modules/browserslist": { "version": "4.24.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", @@ -7310,30 +6919,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -7361,31 +6946,6 @@ "node": ">= 0.8" } }, - "node_modules/c8": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", - "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" - }, - "bin": { - "c8": "bin/c8.js" - }, - "engines": { - "node": ">=14.14.0" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -7595,33 +7155,6 @@ "node": ">=6" } }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dev": true, - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -7980,18 +7513,6 @@ } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -9197,15 +8718,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -9863,15 +9375,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -10169,18 +9672,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.0.tgz", @@ -10320,18 +9811,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -10389,15 +9868,6 @@ "node": ">=8" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -10525,18 +9995,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -12077,22 +11535,6 @@ "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", "dev": true }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", @@ -12480,183 +11922,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/mocha/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/mocha/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/monaco-vscode-textmate-theme-converter": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/monaco-vscode-textmate-theme-converter/-/monaco-vscode-textmate-theme-converter-0.1.7.tgz", @@ -12801,6 +12066,7 @@ "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "chalk": "^2.4.1", @@ -13136,92 +12402,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", - "dev": true, - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.3.0", - "log-symbols": "^5.1.0", - "stdin-discarder": "^0.1.0", - "string-width": "^6.1.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true - }, - "node_modules/ora/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "dev": true, - "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/string-width": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^10.2.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/os-name": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/os-name/-/os-name-6.0.0.tgz", @@ -13895,15 +13075,6 @@ "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/raw-body": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", @@ -14152,28 +13323,6 @@ "node": ">=10" } }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -14409,15 +13558,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -14750,21 +13890,6 @@ "node": ">= 0.8" } }, - "node_modules/stdin-discarder": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", - "dev": true, - "dependencies": { - "bl": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/streamx": { "version": "2.21.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.0.tgz", @@ -15051,18 +14176,6 @@ "integrity": "sha512-nMIjMrd5Z2nuB2RZCKJfFMjgS3fygbeyGk9PxPPaJR1RIcyN9yn4A63Isovzm3ZtQuEkLBVgMdPup8UeLH7aQw==", "dev": true }, - "node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -15966,12 +15079,6 @@ "node": ">=0.10.0" } }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true - }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -16170,33 +15277,6 @@ "node": ">=12" } }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yargs/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", diff --git a/package.json b/package.json index fe64af16d6..b9b62bd5e3 100644 --- a/package.json +++ b/package.json @@ -276,21 +276,24 @@ "scripts": { "build": "npm run build:webview && npm run vsix", "build:webview": "cd webview-ui && npm run build", - "changeset": "changeset", - "check-types": "tsc --noEmit && cd webview-ui && npm run check-types", "compile": "tsc -p . --outDir out && node esbuild.js", - "compile:integration": "tsc -p tsconfig.integration.json", - "install:all": "npm install && cd webview-ui && npm install", - "knip": "knip --include files", - "lint": "eslint src --ext ts && npm run lint --prefix webview-ui", - "lint-local": "eslint -c .eslintrc.local.json src --ext ts && npm run lint --prefix webview-ui", - "lint-fix": "eslint src --ext ts --fix && npm run lint-fix --prefix webview-ui", - "lint-fix-local": "eslint -c .eslintrc.local.json src --ext ts --fix && npm run lint-fix --prefix webview-ui", + "install:all": "npm-run-all -p install-*", + "install:ci": "npm install npm-run-all && npm run install:all", + "install-extension": "npm install", + "install-webview-ui": "cd webview-ui && npm install", + "install-e2e": "cd e2e && npm install", + "lint": "npm-run-all -p lint:*", + "lint:extension": "eslint src --ext ts", + "lint:webview-ui": "cd webview-ui && npm run lint", + "lint:e2e": "cd e2e && npm run lint", + "check-types": "npm-run-all -p check-types:*", + "check-types:extension": "tsc --noEmit", + "check-types:webview-ui": "cd webview-ui && npm run check-types", + "check-types:e2e": "cd e2e && npm run check-types", "package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production", - "pretest": "npm run compile && npm run compile:integration", + "pretest": "npm run compile", "dev": "cd webview-ui && npm run dev", "test": "jest && cd webview-ui && npm run test", - "test:integration": "npm run build && npm run compile:integration && npx dotenvx run -f .env.integration -- node ./out-integration/test/runTest.js", "prepare": "husky", "publish:marketplace": "vsce publish && ovsx publish", "publish": "npm run build && changeset publish && npm install --package-lock-only", @@ -300,7 +303,9 @@ "watch": "npm-run-all -p watch:*", "watch:esbuild": "node esbuild.js --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", - "watch-tests": "tsc -p . -w --outDir out" + "watch-tests": "tsc -p . -w --outDir out", + "changeset": "changeset", + "knip": "knip --include files" }, "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", @@ -358,13 +363,10 @@ "@types/diff-match-patch": "^1.0.36", "@types/glob": "^8.1.0", "@types/jest": "^29.5.14", - "@types/mocha": "^10.0.10", "@types/node": "20.x", "@types/string-similarity": "^4.0.2", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.11.0", - "@vscode/test-cli": "^0.0.9", - "@vscode/test-electron": "^2.4.0", "esbuild": "^0.24.0", "eslint": "^8.57.0", "glob": "^11.0.1", @@ -374,7 +376,6 @@ "knip": "^5.44.4", "lint-staged": "^15.2.11", "mkdirp": "^3.0.1", - "mocha": "^11.1.0", "npm-run-all": "^4.1.5", "prettier": "^3.4.2", "rimraf": "^6.0.1", diff --git a/src/exports/cline.d.ts b/src/exports/cline.d.ts index fcf93fc10d..e529947b6b 100644 --- a/src/exports/cline.d.ts +++ b/src/exports/cline.d.ts @@ -40,3 +40,96 @@ export interface ClineAPI { */ sidebarProvider: ClineSidebarProvider } + +export interface ClineProvider { + readonly context: vscode.ExtensionContext + readonly viewLaunched: boolean + readonly messages: ClineMessage[] + + /** + * Resolves the webview view for the provider + * @param webviewView The webview view or panel to resolve + */ + resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel): Promise + + /** + * Initializes Cline with a task + */ + initClineWithTask(task?: string, images?: string[]): Promise + + /** + * Initializes Cline with a history item + */ + initClineWithHistoryItem(historyItem: HistoryItem): Promise + + /** + * Posts a message to the webview + */ + postMessageToWebview(message: ExtensionMessage): Promise + + /** + * Handles mode switching + */ + handleModeSwitch(newMode: Mode): Promise + + /** + * Updates custom instructions + */ + updateCustomInstructions(instructions?: string): Promise + + /** + * Cancels the current task + */ + cancelTask(): Promise + + /** + * Clears the current task + */ + clearTask(): Promise + + /** + * Gets the current state + */ + getState(): Promise + + /** + * Updates a value in the global state + * @param key The key to update + * @param value The value to set + */ + updateGlobalState(key: GlobalStateKey, value: any): Promise + + /** + * Gets a value from the global state + * @param key The key to get + */ + getGlobalState(key: GlobalStateKey): Promise + + /** + * Stores a secret value in secure storage + * @param key The key to store the secret under + * @param value The secret value to store, or undefined to remove the secret + */ + storeSecret(key: SecretKey, value?: string): Promise + + /** + * Retrieves a secret value from secure storage + * @param key The key of the secret to retrieve + */ + getSecret(key: SecretKey): Promise + + /** + * Resets the state + */ + resetState(): Promise + + /** + * Logs a message + */ + log(message: string): void + + /** + * Disposes of the provider + */ + dispose(): Promise +} diff --git a/src/shared/modes.ts b/src/shared/modes.ts index ceb1e3e11f..2eda966519 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -36,7 +36,11 @@ export type CustomModePrompts = { // Helper to extract group name regardless of format export function getGroupName(group: GroupEntry): ToolGroup { - return Array.isArray(group) ? group[0] : group + if (typeof group === "string") { + return group + } + + return group[0] } // Helper to get group options if they exist From 481d613bc8701d880af5af598f5c0d9d75e00935 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 28 Feb 2025 21:34:53 -0800 Subject: [PATCH 11/40] Estimate the number of tokens in the last message for sliding window math --- e2e/tsconfig.json | 2 +- package-lock.json | 9 + package.json | 1 + .../__tests__/sliding-window.test.ts | 224 ++++++++++++++++-- src/core/sliding-window/index.ts | 70 +++++- 5 files changed, 276 insertions(+), 30 deletions(-) diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index 9958110767..792acb14a0 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -11,6 +11,6 @@ "useUnknownInCatchVariables": false, "outDir": "out" }, - "include": ["src", "../src/exports"], + "include": ["src", "../src/exports/cline.d.ts"], "exclude": [".vscode-test", "**/node_modules/**", "out"] } diff --git a/package-lock.json b/package-lock.json index a8fa18b5f1..0b24ce6664 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "get-folder-size": "^5.0.0", "globby": "^14.0.2", "isbinaryfile": "^5.0.2", + "js-tiktoken": "^1.0.19", "mammoth": "^1.8.0", "monaco-vscode-textmate-theme-converter": "^0.1.7", "openai": "^4.78.1", @@ -10909,6 +10910,14 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tiktoken": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.19.tgz", + "integrity": "sha512-XC63YQeEcS47Y53gg950xiZ4IWmkfMe4p2V9OSaBt26q+p47WHn18izuXzSclCI73B7yGqtfRsT6jcZQI0y08g==", + "dependencies": { + "base64-js": "^1.5.1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index b9b62bd5e3..6f5699a7c7 100644 --- a/package.json +++ b/package.json @@ -348,6 +348,7 @@ "sound-play": "^1.1.0", "string-similarity": "^4.0.4", "strip-ansi": "^7.1.0", + "js-tiktoken": "^1.0.19", "tmp": "^0.2.3", "tree-sitter-wasms": "^0.1.11", "turndown": "^7.2.0", diff --git a/src/core/sliding-window/__tests__/sliding-window.test.ts b/src/core/sliding-window/__tests__/sliding-window.test.ts index cb897aa8cb..74b734d738 100644 --- a/src/core/sliding-window/__tests__/sliding-window.test.ts +++ b/src/core/sliding-window/__tests__/sliding-window.test.ts @@ -3,7 +3,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import { ModelInfo } from "../../../shared/api" -import { truncateConversation, truncateConversationIfNeeded } from "../index" +import { estimateTokenCount, truncateConversation, truncateConversationIfNeeded } from "../index" /** * Tests for the truncateConversation function @@ -118,23 +118,26 @@ describe("getMaxTokens", () => { const modelInfo = createModelInfo(100000, 50000) // Max tokens = 100000 - 50000 = 50000 + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + // Below max tokens - no truncation const result1 = truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: 49999, contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) - expect(result1).toEqual(messages) + expect(result1).toEqual(messagesWithSmallContent) // Above max tokens - truncate const result2 = truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: 50001, contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) - expect(result2).not.toEqual(messages) + expect(result2).not.toEqual(messagesWithSmallContent) expect(result2.length).toBe(3) // Truncated with 0.5 fraction }) @@ -142,23 +145,26 @@ describe("getMaxTokens", () => { const modelInfo = createModelInfo(100000, undefined) // Max tokens = 100000 - (100000 * 0.2) = 80000 + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + // Below max tokens - no truncation const result1 = truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: 79999, contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) - expect(result1).toEqual(messages) + expect(result1).toEqual(messagesWithSmallContent) // Above max tokens - truncate const result2 = truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: 80001, contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) - expect(result2).not.toEqual(messages) + expect(result2).not.toEqual(messagesWithSmallContent) expect(result2.length).toBe(3) // Truncated with 0.5 fraction }) @@ -166,23 +172,26 @@ describe("getMaxTokens", () => { const modelInfo = createModelInfo(50000, 10000) // Max tokens = 50000 - 10000 = 40000 + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + // Below max tokens - no truncation const result1 = truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: 39999, contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) - expect(result1).toEqual(messages) + expect(result1).toEqual(messagesWithSmallContent) // Above max tokens - truncate const result2 = truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: 40001, contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) - expect(result2).not.toEqual(messages) + expect(result2).not.toEqual(messagesWithSmallContent) expect(result2.length).toBe(3) // Truncated with 0.5 fraction }) @@ -190,23 +199,26 @@ describe("getMaxTokens", () => { const modelInfo = createModelInfo(200000, 30000) // Max tokens = 200000 - 30000 = 170000 + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + // Below max tokens - no truncation const result1 = truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: 169999, contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) - expect(result1).toEqual(messages) + expect(result1).toEqual(messagesWithSmallContent) // Above max tokens - truncate const result2 = truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: 170001, contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) - expect(result2).not.toEqual(messages) + expect(result2).not.toEqual(messagesWithSmallContent) expect(result2.length).toBe(3) // Truncated with 0.5 fraction }) }) @@ -234,13 +246,16 @@ describe("truncateConversationIfNeeded", () => { const maxTokens = 100000 - 30000 // 70000 const totalTokens = 69999 // Below threshold + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + const result = truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens, contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) - expect(result).toEqual(messages) // No truncation occurs + expect(result).toEqual(messagesWithSmallContent) // No truncation occurs }) it("should truncate if tokens are above max tokens threshold", () => { @@ -248,12 +263,15 @@ describe("truncateConversationIfNeeded", () => { const maxTokens = 100000 - 30000 // 70000 const totalTokens = 70001 // Above threshold + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + // When truncating, always uses 0.5 fraction // With 4 messages after the first, 0.5 fraction means remove 2 messages - const expectedResult = [messages[0], messages[3], messages[4]] + const expectedResult = [messagesWithSmallContent[0], messagesWithSmallContent[3], messagesWithSmallContent[4]] const result = truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens, contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, @@ -266,18 +284,21 @@ describe("truncateConversationIfNeeded", () => { const modelInfo1 = createModelInfo(100000, true, 30000) const modelInfo2 = createModelInfo(100000, false, 30000) + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + // Test below threshold const belowThreshold = 69999 expect( truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: belowThreshold, contextWindow: modelInfo1.contextWindow, maxTokens: modelInfo1.maxTokens, }), ).toEqual( truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: belowThreshold, contextWindow: modelInfo2.contextWindow, maxTokens: modelInfo2.maxTokens, @@ -288,18 +309,171 @@ describe("truncateConversationIfNeeded", () => { const aboveThreshold = 70001 expect( truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: aboveThreshold, contextWindow: modelInfo1.contextWindow, maxTokens: modelInfo1.maxTokens, }), ).toEqual( truncateConversationIfNeeded({ - messages, + messages: messagesWithSmallContent, totalTokens: aboveThreshold, contextWindow: modelInfo2.contextWindow, maxTokens: modelInfo2.maxTokens, }), ) }) + + it("should consider incoming content when deciding to truncate", () => { + const modelInfo = createModelInfo(100000, true, 30000) + const maxTokens = 30000 + const availableTokens = modelInfo.contextWindow - maxTokens + + // Test case 1: Small content that won't push us over the threshold + const smallContent = [{ type: "text" as const, text: "Small content" }] + const smallContentTokens = estimateTokenCount(smallContent) + const messagesWithSmallContent: Anthropic.Messages.MessageParam[] = [ + ...messages.slice(0, -1), + { role: messages[messages.length - 1].role, content: smallContent }, + ] + + // Set base tokens so total is below threshold even with small content added + const baseTokensForSmall = availableTokens - smallContentTokens - 10 + const resultWithSmall = truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens: baseTokensForSmall, + contextWindow: modelInfo.contextWindow, + maxTokens, + }) + expect(resultWithSmall).toEqual(messagesWithSmallContent) // No truncation + + // Test case 2: Large content that will push us over the threshold + const largeContent = [ + { + type: "text" as const, + text: "A very large incoming message that would consume a significant number of tokens and push us over the threshold", + }, + ] + const largeContentTokens = estimateTokenCount(largeContent) + const messagesWithLargeContent: Anthropic.Messages.MessageParam[] = [ + ...messages.slice(0, -1), + { role: messages[messages.length - 1].role, content: largeContent }, + ] + + // Set base tokens so we're just below threshold without content, but over with content + const baseTokensForLarge = availableTokens - Math.floor(largeContentTokens / 2) + const resultWithLarge = truncateConversationIfNeeded({ + messages: messagesWithLargeContent, + totalTokens: baseTokensForLarge, + contextWindow: modelInfo.contextWindow, + maxTokens, + }) + expect(resultWithLarge).not.toEqual(messagesWithLargeContent) // Should truncate + + // Test case 3: Very large content that will definitely exceed threshold + const veryLargeContent = [{ type: "text" as const, text: "X".repeat(1000) }] + const veryLargeContentTokens = estimateTokenCount(veryLargeContent) + const messagesWithVeryLargeContent: Anthropic.Messages.MessageParam[] = [ + ...messages.slice(0, -1), + { role: messages[messages.length - 1].role, content: veryLargeContent }, + ] + + // Set base tokens so we're just below threshold without content + const baseTokensForVeryLarge = availableTokens - Math.floor(veryLargeContentTokens / 2) + const resultWithVeryLarge = truncateConversationIfNeeded({ + messages: messagesWithVeryLargeContent, + totalTokens: baseTokensForVeryLarge, + contextWindow: modelInfo.contextWindow, + maxTokens, + }) + expect(resultWithVeryLarge).not.toEqual(messagesWithVeryLargeContent) // Should truncate + }) +}) +/** + * Tests for the estimateTokenCount function + */ +describe("estimateTokenCount", () => { + it("should return 0 for empty or undefined content", () => { + expect(estimateTokenCount([])).toBe(0) + // @ts-ignore - Testing with undefined + expect(estimateTokenCount(undefined)).toBe(0) + }) + + it("should estimate tokens for text blocks", () => { + const content: Array = [ + { type: "text", text: "This is a text block with 36 characters" }, + ] + + // With tiktoken, the exact token count may differ from character-based estimation + // Instead of expecting an exact number, we verify it's a reasonable positive number + const result = estimateTokenCount(content) + expect(result).toBeGreaterThan(0) + + // We can also verify that longer text results in more tokens + const longerContent: Array = [ + { + type: "text", + text: "This is a longer text block with significantly more characters to encode into tokens", + }, + ] + const longerResult = estimateTokenCount(longerContent) + expect(longerResult).toBeGreaterThan(result) + }) + + it("should estimate tokens for image blocks based on data size", () => { + // Small image + const smallImage: Array = [ + { type: "image", source: { type: "base64", media_type: "image/jpeg", data: "small_dummy_data" } }, + ] + // Larger image with more data + const largerImage: Array = [ + { type: "image", source: { type: "base64", media_type: "image/png", data: "X".repeat(1000) } }, + ] + + // Verify the token count scales with the size of the image data + const smallImageTokens = estimateTokenCount(smallImage) + const largerImageTokens = estimateTokenCount(largerImage) + + // Small image should have some tokens + expect(smallImageTokens).toBeGreaterThan(0) + + // Larger image should have proportionally more tokens + expect(largerImageTokens).toBeGreaterThan(smallImageTokens) + + // Verify the larger image calculation matches our formula including the 50% fudge factor + expect(largerImageTokens).toBe(48) + }) + + it("should estimate tokens for mixed content blocks", () => { + const content: Array = [ + { type: "text", text: "A text block with 30 characters" }, + { type: "image", source: { type: "base64", media_type: "image/jpeg", data: "dummy_data" } }, + { type: "text", text: "Another text with 24 chars" }, + ] + + // We know image tokens calculation should be consistent + const imageTokens = Math.ceil(Math.sqrt("dummy_data".length)) * 1.5 + + // With tiktoken, we can't predict exact text token counts, + // but we can verify the total is greater than just the image tokens + const result = estimateTokenCount(content) + expect(result).toBeGreaterThan(imageTokens) + + // Also test against a version with only the image to verify text adds tokens + const imageOnlyContent: Array = [ + { type: "image", source: { type: "base64", media_type: "image/jpeg", data: "dummy_data" } }, + ] + const imageOnlyResult = estimateTokenCount(imageOnlyContent) + expect(result).toBeGreaterThan(imageOnlyResult) + }) + + it("should handle empty text blocks", () => { + const content: Array = [{ type: "text", text: "" }] + expect(estimateTokenCount(content)).toBe(0) + }) + + it("should handle plain string messages", () => { + const content = "This is a plain text message" + expect(estimateTokenCount([{ type: "text", text: content }])).toBeGreaterThan(0) + }) }) diff --git a/src/core/sliding-window/index.ts b/src/core/sliding-window/index.ts index 8b646f933b..0fb26ac38f 100644 --- a/src/core/sliding-window/index.ts +++ b/src/core/sliding-window/index.ts @@ -1,5 +1,51 @@ import { Anthropic } from "@anthropic-ai/sdk" +import { Tiktoken } from "js-tiktoken/lite" +import o200kBase from "js-tiktoken/ranks/o200k_base" + +const TOKEN_FUDGE_FACTOR = 1.5 + +/** + * Counts tokens for user content using tiktoken for text + * and a size-based calculation for images. + * + * @param {Array} content - The content to count tokens for + * @returns {number} The token count + */ +export function estimateTokenCount(content: Array): number { + if (!content || content.length === 0) return 0 + + let totalTokens = 0 + let encoder = null + + // Create encoder + encoder = new Tiktoken(o200kBase) + + // Process each content block + for (const block of content) { + if (block.type === "text") { + // Use tiktoken for text token counting + const text = block.text || "" + if (text.length > 0) { + const tokens = encoder.encode(text) + totalTokens += tokens.length + } + } else if (block.type === "image") { + // For images, calculate based on data size + const imageSource = block.source + if (imageSource && typeof imageSource === "object" && "data" in imageSource) { + const base64Data = imageSource.data as string + totalTokens += Math.ceil(Math.sqrt(base64Data.length)) + } else { + totalTokens += 300 // Conservative estimate for unknown images + } + } + } + + // Add a fudge factor to account for the fact that tiktoken is not always accurate + return Math.ceil(totalTokens * TOKEN_FUDGE_FACTOR) +} + /** * Truncates a conversation by removing a fraction of the messages. * @@ -25,10 +71,10 @@ export function truncateConversation( /** * Conditionally truncates the conversation messages if the total token count - * exceeds the model's limit. + * exceeds the model's limit, considering the size of incoming content. * * @param {Anthropic.Messages.MessageParam[]} messages - The conversation messages. - * @param {number} totalTokens - The total number of tokens in the conversation. + * @param {number} totalTokens - The total number of tokens in the conversation (excluding the last user message). * @param {number} contextWindow - The context window size. * @param {number} maxTokens - The maximum number of tokens allowed. * @returns {Anthropic.Messages.MessageParam[]} The original or truncated conversation messages. @@ -47,6 +93,22 @@ export function truncateConversationIfNeeded({ contextWindow, maxTokens, }: TruncateOptions): Anthropic.Messages.MessageParam[] { - const allowedTokens = contextWindow - (maxTokens || contextWindow * 0.2) - return totalTokens < allowedTokens ? messages : truncateConversation(messages, 0.5) + // Calculate the maximum tokens reserved for response + const reservedTokens = maxTokens || contextWindow * 0.2 + + // Estimate tokens for the last message (which is always a user message) + const lastMessage = messages[messages.length - 1] + const lastMessageContent = lastMessage.content + const lastMessageTokens = Array.isArray(lastMessageContent) + ? estimateTokenCount(lastMessageContent) + : estimateTokenCount([{ type: "text", text: lastMessageContent as string }]) + + // Calculate total effective tokens (totalTokens never includes the last message) + const effectiveTokens = totalTokens + lastMessageTokens + + // Calculate available tokens for conversation history + const allowedTokens = contextWindow - reservedTokens + + // Determine if truncation is needed and apply if necessary + return effectiveTokens < allowedTokens ? messages : truncateConversation(messages, 0.5) } From e009d5f14c81bd8b89da288f1d43adfe1e1af544 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 28 Feb 2025 21:40:47 -0800 Subject: [PATCH 12/40] Fix e2e test paths --- e2e/.vscode-test.mjs | 2 +- e2e/package.json | 2 +- e2e/src/runTest.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/.vscode-test.mjs b/e2e/.vscode-test.mjs index 16ea4271ba..ccc8b495ea 100644 --- a/e2e/.vscode-test.mjs +++ b/e2e/.vscode-test.mjs @@ -6,7 +6,7 @@ import { defineConfig } from '@vscode/test-cli'; export default defineConfig({ label: 'integrationTest', - files: 'out/e2e/src/suite/**/*.test.js', + files: 'out/suite/**/*.test.js', workspaceFolder: '.', mocha: { ui: 'tdd', diff --git a/e2e/package.json b/e2e/package.json index 7e0daac064..630a13f0e7 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -7,7 +7,7 @@ "compile": "tsc -p tsconfig.json", "lint": "eslint src --ext ts", "check-types": "tsc --noEmit", - "test": "npm run compile && npx dotenvx run -f .env.integration -- node ./out/e2e/src/runTest.js", + "test": "npm run compile && npx dotenvx run -f .env.integration -- node ./out/runTest.js", "ci": "npm run build && npm run test" }, "dependencies": {}, diff --git a/e2e/src/runTest.ts b/e2e/src/runTest.ts index 28545efd46..2576c6072f 100644 --- a/e2e/src/runTest.ts +++ b/e2e/src/runTest.ts @@ -6,7 +6,7 @@ async function main() { try { // The folder containing the Extension Manifest package.json // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, "../../../../") + const extensionDevelopmentPath = path.resolve(__dirname, "../../") // The path to the extension test script // Passed to --extensionTestsPath From 764d963c104be512187ee9cb600029952c58d0a0 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 1 Mar 2025 08:49:10 -0500 Subject: [PATCH 13/40] Add a 5k token buffer before the end of the context window --- .../__tests__/sliding-window.test.ts | 54 +++++++++++++------ src/core/sliding-window/index.ts | 6 ++- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/core/sliding-window/__tests__/sliding-window.test.ts b/src/core/sliding-window/__tests__/sliding-window.test.ts index 74b734d738..dbc0c678c2 100644 --- a/src/core/sliding-window/__tests__/sliding-window.test.ts +++ b/src/core/sliding-window/__tests__/sliding-window.test.ts @@ -3,7 +3,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import { ModelInfo } from "../../../shared/api" -import { estimateTokenCount, truncateConversation, truncateConversationIfNeeded } from "../index" +import { TOKEN_BUFFER, estimateTokenCount, truncateConversation, truncateConversationIfNeeded } from "../index" /** * Tests for the truncateConversation function @@ -121,10 +121,10 @@ describe("getMaxTokens", () => { // Create messages with very small content in the last one to avoid token overflow const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] - // Below max tokens - no truncation + // Below max tokens and buffer - no truncation const result1 = truncateConversationIfNeeded({ messages: messagesWithSmallContent, - totalTokens: 49999, + totalTokens: 44999, // Well below threshold + buffer contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) @@ -133,7 +133,7 @@ describe("getMaxTokens", () => { // Above max tokens - truncate const result2 = truncateConversationIfNeeded({ messages: messagesWithSmallContent, - totalTokens: 50001, + totalTokens: 50001, // Above threshold contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) @@ -148,10 +148,10 @@ describe("getMaxTokens", () => { // Create messages with very small content in the last one to avoid token overflow const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] - // Below max tokens - no truncation + // Below max tokens and buffer - no truncation const result1 = truncateConversationIfNeeded({ messages: messagesWithSmallContent, - totalTokens: 79999, + totalTokens: 74999, // Well below threshold + buffer contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) @@ -160,7 +160,7 @@ describe("getMaxTokens", () => { // Above max tokens - truncate const result2 = truncateConversationIfNeeded({ messages: messagesWithSmallContent, - totalTokens: 80001, + totalTokens: 80001, // Above threshold contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) @@ -175,10 +175,10 @@ describe("getMaxTokens", () => { // Create messages with very small content in the last one to avoid token overflow const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] - // Below max tokens - no truncation + // Below max tokens and buffer - no truncation const result1 = truncateConversationIfNeeded({ messages: messagesWithSmallContent, - totalTokens: 39999, + totalTokens: 34999, // Well below threshold + buffer contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) @@ -187,7 +187,7 @@ describe("getMaxTokens", () => { // Above max tokens - truncate const result2 = truncateConversationIfNeeded({ messages: messagesWithSmallContent, - totalTokens: 40001, + totalTokens: 40001, // Above threshold contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) @@ -202,10 +202,10 @@ describe("getMaxTokens", () => { // Create messages with very small content in the last one to avoid token overflow const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] - // Below max tokens - no truncation + // Below max tokens and buffer - no truncation const result1 = truncateConversationIfNeeded({ messages: messagesWithSmallContent, - totalTokens: 169999, + totalTokens: 164999, // Well below threshold + buffer contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) @@ -214,7 +214,7 @@ describe("getMaxTokens", () => { // Above max tokens - truncate const result2 = truncateConversationIfNeeded({ messages: messagesWithSmallContent, - totalTokens: 170001, + totalTokens: 170001, // Above threshold contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, }) @@ -244,7 +244,7 @@ describe("truncateConversationIfNeeded", () => { it("should not truncate if tokens are below max tokens threshold", () => { const modelInfo = createModelInfo(100000, true, 30000) const maxTokens = 100000 - 30000 // 70000 - const totalTokens = 69999 // Below threshold + const totalTokens = 64999 // Well below threshold + buffer // Create messages with very small content in the last one to avoid token overflow const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] @@ -337,8 +337,8 @@ describe("truncateConversationIfNeeded", () => { { role: messages[messages.length - 1].role, content: smallContent }, ] - // Set base tokens so total is below threshold even with small content added - const baseTokensForSmall = availableTokens - smallContentTokens - 10 + // Set base tokens so total is well below threshold + buffer even with small content added + const baseTokensForSmall = availableTokens - smallContentTokens - TOKEN_BUFFER - 10 const resultWithSmall = truncateConversationIfNeeded({ messages: messagesWithSmallContent, totalTokens: baseTokensForSmall, @@ -388,7 +388,29 @@ describe("truncateConversationIfNeeded", () => { }) expect(resultWithVeryLarge).not.toEqual(messagesWithVeryLargeContent) // Should truncate }) + + it("should truncate if tokens are within TOKEN_BUFFER of the threshold", () => { + const modelInfo = createModelInfo(100000, true, 30000) + const maxTokens = 100000 - 30000 // 70000 + const totalTokens = 66000 // Within 5000 of threshold (70000) + + // Create messages with very small content in the last one to avoid token overflow + const messagesWithSmallContent = [...messages.slice(0, -1), { ...messages[messages.length - 1], content: "" }] + + // When truncating, always uses 0.5 fraction + // With 4 messages after the first, 0.5 fraction means remove 2 messages + const expectedResult = [messagesWithSmallContent[0], messagesWithSmallContent[3], messagesWithSmallContent[4]] + + const result = truncateConversationIfNeeded({ + messages: messagesWithSmallContent, + totalTokens, + contextWindow: modelInfo.contextWindow, + maxTokens: modelInfo.maxTokens, + }) + expect(result).toEqual(expectedResult) + }) }) + /** * Tests for the estimateTokenCount function */ diff --git a/src/core/sliding-window/index.ts b/src/core/sliding-window/index.ts index 0fb26ac38f..d12e7f337e 100644 --- a/src/core/sliding-window/index.ts +++ b/src/core/sliding-window/index.ts @@ -3,7 +3,8 @@ import { Anthropic } from "@anthropic-ai/sdk" import { Tiktoken } from "js-tiktoken/lite" import o200kBase from "js-tiktoken/ranks/o200k_base" -const TOKEN_FUDGE_FACTOR = 1.5 +export const TOKEN_FUDGE_FACTOR = 1.5 +export const TOKEN_BUFFER = 5000 /** * Counts tokens for user content using tiktoken for text @@ -110,5 +111,6 @@ export function truncateConversationIfNeeded({ const allowedTokens = contextWindow - reservedTokens // Determine if truncation is needed and apply if necessary - return effectiveTokens < allowedTokens ? messages : truncateConversation(messages, 0.5) + // Truncate if we're within TOKEN_BUFFER of the limit + return effectiveTokens > allowedTokens - TOKEN_BUFFER ? truncateConversation(messages, 0.5) : messages } From 251b83629aba03727ce12a598e1bbad84eafa678 Mon Sep 17 00:00:00 2001 From: aheizi Date: Fri, 28 Feb 2025 21:52:48 +0800 Subject: [PATCH 14/40] feat: add shortcut to switch modes --- .../src/components/chat/ChatTextArea.tsx | 7 ++++ webview-ui/src/components/chat/ChatView.tsx | 32 +++++++++++++++++++ .../chat/__tests__/ChatTextArea.test.tsx | 1 + 3 files changed, 40 insertions(+) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index dcbe085147..a2af6a3a71 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -31,6 +31,7 @@ interface ChatTextAreaProps { onHeightChange?: (height: number) => void mode: Mode setMode: (value: Mode) => void + modeShortcutText: string } const ChatTextArea = forwardRef( @@ -48,6 +49,7 @@ const ChatTextArea = forwardRef( onHeightChange, mode, setMode, + modeShortcutText, }, ref, ) => { @@ -816,6 +818,11 @@ const ChatTextArea = forwardRef( minWidth: "70px", flex: "0 0 auto", }}> + {getAllModes(customModes).map((mode) => (