From aceaa407ff00f24ea122c35140cd49591f9ce424 Mon Sep 17 00:00:00 2001 From: Tajudeen Date: Wed, 19 Nov 2025 23:05:43 +0000 Subject: [PATCH 01/44] feat: add update notification system similar to Cursor - Add post-install/update notification system that shows when version changes - Update all GitHub URLs to use opencortexide organization - Add releaseNotesUrl to product.json - Track last seen version in storage to detect new installations/updates - Show welcome message for new installations and update notifications - Link to GitHub releases for 'What's New' feature --- extensions/open-remote-ssh/package.json | 2 +- product.json | 9 +- .../browser/cortexide.contribution.ts | 1 + .../browser/cortexideUpdateNotification.ts | 149 ++++++++++++++++++ .../react/src/void-settings-tsx/Settings.tsx | 2 +- 5 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 src/vs/workbench/contrib/cortexide/browser/cortexideUpdateNotification.ts diff --git a/extensions/open-remote-ssh/package.json b/extensions/open-remote-ssh/package.json index 0c9965fb4ae..fbbd9149f60 100644 --- a/extensions/open-remote-ssh/package.json +++ b/extensions/open-remote-ssh/package.json @@ -72,7 +72,7 @@ "type": "string", "description": "The URL from where the vscode server will be downloaded. You can use the following variables and they will be replaced dynamically:\n- ${quality}: vscode server quality, e.g. stable or insiders\n- ${version}: vscode server version, e.g. 1.69.0\n- ${commit}: vscode server release commit\n- ${arch}: vscode server arch, e.g. x64, armhf, arm64\n- ${release}: release number", "scope": "application", - "default": "https://github.com/cortexide/cortexide/releases/download/${version}/cortexide-reh-${os}-${arch}-${version}.tar.gz" + "default": "https://github.com/opencortexide/cortexide/releases/download/${version}/cortexide-reh-${os}-${arch}-${version}.tar.gz" }, "remote.SSH.remotePlatform": { "type": "object", diff --git a/product.json b/product.json index e64597be3fd..f55a3a712fd 100644 --- a/product.json +++ b/product.json @@ -7,8 +7,8 @@ "dataFolderName": ".cortexide", "win32MutexName": "cortexide", "licenseName": "MIT", - "licenseUrl": "https://github.com/cortexide/cortexide/blob/main/LICENSE.txt", - "serverLicenseUrl": "https://github.com/cortexide/cortexide/blob/main/LICENSE.txt", + "licenseUrl": "https://github.com/opencortexide/cortexide/blob/main/LICENSE.txt", + "serverLicenseUrl": "https://github.com/opencortexide/cortexide/blob/main/LICENSE.txt", "serverGreeting": [], "serverLicense": [], "serverLicensePrompt": "", @@ -29,7 +29,8 @@ "darwinBundleIdentifier": "com.cortexide.code", "linuxIconName": "cortexide", "licenseFileName": "LICENSE.txt", - "reportIssueUrl": "https://github.com/cortexide/cortexide/issues/new", + "reportIssueUrl": "https://github.com/opencortexide/cortexide/issues/new", + "releaseNotesUrl": "https://github.com/opencortexide/cortexide/releases", "nodejsRepository": "https://nodejs.org", "urlProtocol": "cortexide", "extensionsGallery": { @@ -39,7 +40,7 @@ "builtInExtensions": [], "linkProtectionTrustedDomains": [ "https://opencortexide.com", - "https://github.com/cortexide/cortexide", + "https://github.com/opencortexide/cortexide", "https://ollama.com" ] , diff --git a/src/vs/workbench/contrib/cortexide/browser/cortexide.contribution.ts b/src/vs/workbench/contrib/cortexide/browser/cortexide.contribution.ts index 7e3b0d59b80..fccb3233da4 100644 --- a/src/vs/workbench/contrib/cortexide/browser/cortexide.contribution.ts +++ b/src/vs/workbench/contrib/cortexide/browser/cortexide.contribution.ts @@ -33,6 +33,7 @@ import './media/cortexide.css' // update (frontend part, also see platform/) import './cortexideUpdateActions.js' +import './cortexideUpdateNotification.js' import './convertToLLMMessageWorkbenchContrib.js' diff --git a/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateNotification.ts b/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateNotification.ts new file mode 100644 index 00000000000..edb45b6ae1f --- /dev/null +++ b/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateNotification.ts @@ -0,0 +1,149 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import Severity from '../../../../base/common/severity.js'; +import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { INotificationService, INotificationActions } from '../../../../platform/notification/common/notification.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IHostService } from '../../../../platform/host/browser/host.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IAction } from '../../../../base/common/actions.js'; +import { localize2 } from '../../../../nls.js'; + +const LAST_SEEN_VERSION_KEY = 'cortexide.update.lastSeenVersion'; +const UPDATE_NOTIFICATION_DISMISSED_KEY = 'cortexide.update.notificationDismissed'; + +/** + * Shows a notification when a new version is detected after installation or update. + * Similar to Cursor's update notification system. + */ +class CortexideUpdateNotificationContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.cortexide.updateNotification'; + + constructor( + @IStorageService private readonly _storageService: IStorageService, + @IProductService private readonly _productService: IProductService, + @INotificationService private readonly _notificationService: INotificationService, + @IHostService private readonly _hostService: IHostService, + @IOpenerService private readonly _openerService: IOpenerService, + ) { + super(); + + // Wait for the window to have focus before showing the notification + this._hostService.hadLastFocus().then(async hadLastFocus => { + if (!hadLastFocus) { + return; + } + + // Small delay to ensure the UI is fully loaded + await new Promise(resolve => setTimeout(resolve, 2000)); + + this._checkAndShowUpdateNotification(); + }); + } + + private _checkAndShowUpdateNotification(): void { + const currentVersion = this._productService.version; + const lastSeenVersion = this._storageService.get(LAST_SEEN_VERSION_KEY, StorageScope.APPLICATION, ''); + + // If this is the first time running or version has changed + if (!lastSeenVersion || lastSeenVersion !== currentVersion) { + // Check if user has already dismissed the notification for this version + const dismissedVersion = this._storageService.get(UPDATE_NOTIFICATION_DISMISSED_KEY, StorageScope.APPLICATION, ''); + + if (dismissedVersion !== currentVersion) { + const isNewInstallation = !lastSeenVersion; + this._showUpdateNotification(currentVersion, isNewInstallation); + } + + // Update the last seen version + this._storageService.store(LAST_SEEN_VERSION_KEY, currentVersion, StorageScope.APPLICATION, StorageTarget.MACHINE); + } + } + + private _showUpdateNotification(version: string, isNewInstallation: boolean): void { + const message = isNewInstallation + ? localize2('updateNotification.welcome', 'Welcome to {0} {1}!', this._productService.nameLong, version) + : localize2('updateNotification.updated', '{0} has been updated to {1}!', this._productService.nameLong, version); + + const primaryActions: IAction[] = []; + + // Add "What's New" button if release notes URL is available + if (this._productService.releaseNotesUrl) { + primaryActions.push({ + id: 'cortexide.update.whatsNew', + label: localize2('updateNotification.whatsNew', 'What\'s New'), + enabled: true, + tooltip: localize2('updateNotification.whatsNewTooltip', 'View release notes'), + class: undefined, + run: async () => { + const uri = URI.parse(this._productService.releaseNotesUrl!); + await this._openerService.open(uri); + } + }); + } else { + // Fallback to GitHub releases if no release notes URL is configured + primaryActions.push({ + id: 'cortexide.update.whatsNew', + label: localize2('updateNotification.whatsNew', 'What\'s New'), + enabled: true, + tooltip: localize2('updateNotification.whatsNewTooltip', 'View release notes'), + class: undefined, + run: async () => { + const uri = URI.parse('https://github.com/opencortexide/cortexide/releases'); + await this._openerService.open(uri); + } + }); + } + + // Add dismiss button + const notificationHandle = this._notificationService.notify({ + severity: Severity.Info, + message: message, + actions: { + primary: primaryActions, + secondary: [ + { + id: 'cortexide.update.dismiss', + label: localize2('updateNotification.dismiss', 'Dismiss'), + enabled: true, + tooltip: localize2('updateNotification.dismissTooltip', 'Dismiss this notification'), + class: undefined, + run: () => { + // Mark this version as dismissed + this._storageService.store( + UPDATE_NOTIFICATION_DISMISSED_KEY, + version, + StorageScope.APPLICATION, + StorageTarget.USER + ); + notificationHandle.close(); + } + } + ] + }, + sticky: isNewInstallation, // Sticky for new installations, auto-dismiss for updates + }); + + // Auto-dismiss after 30 seconds if it's not a new installation + if (!isNewInstallation) { + setTimeout(() => { + if (!notificationHandle.isDisposed) { + notificationHandle.close(); + } + }, 30000); + } + } +} + +registerWorkbenchContribution2( + CortexideUpdateNotificationContribution.ID, + CortexideUpdateNotificationContribution, + WorkbenchPhase.Eventually +); + diff --git a/src/vs/workbench/contrib/cortexide/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/cortexide/browser/react/src/void-settings-tsx/Settings.tsx index b9011c3a5cf..d2210b7d3b9 100644 --- a/src/vs/workbench/contrib/cortexide/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/cortexide/browser/react/src/void-settings-tsx/Settings.tsx @@ -350,7 +350,7 @@ const SimpleModelSettingsDialog = ({ onClose(); }; - const sourcecodeOverridesLink = `https://github.com/cortexide/cortexide/blob/main/src/vs/workbench/contrib/cortexide/common/modelCapabilities.ts#L146-L172` + const sourcecodeOverridesLink = `https://github.com/opencortexide/cortexide/blob/main/src/vs/workbench/contrib/cortexide/common/modelCapabilities.ts#L146-L172` return (
Date: Wed, 19 Nov 2025 23:15:43 +0000 Subject: [PATCH 02/44] fix: update all void URLs to use opencortexide organization - Fix compilation errors in update notification system - Update GitHub URLs to use opencortexide organization - Replace voideditor.com email with GitHub issues link - Update product name references from Void to CortexIDE - Fix IHostService import path - Convert localize2 calls to use .value for string conversion --- .../browser/cortexideUpdateActions.ts | 2 +- .../browser/cortexideUpdateNotification.ts | 32 +++++++++++-------- .../src/void-onboarding/VoidOnboarding.tsx | 2 +- .../common/cortexideSettingsTypes.ts | 2 +- .../cortexideUpdateMainService.ts | 2 +- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateActions.ts b/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateActions.ts index 5bb4e1ba6b5..778d14ed1aa 100644 --- a/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateActions.ts +++ b/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateActions.ts @@ -37,7 +37,7 @@ const notifyUpdate = (res: CortexideCheckUpdateResponse & { message: string }, n class: undefined, run: () => { const { window } = dom.getActiveWindow() - window.open('https://voideditor.com/download-beta') + window.open('https://github.com/opencortexide/cortexide/releases') } }) } diff --git a/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateNotification.ts b/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateNotification.ts index edb45b6ae1f..6a9b6dad211 100644 --- a/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateNotification.ts +++ b/src/vs/workbench/contrib/cortexide/browser/cortexideUpdateNotification.ts @@ -6,10 +6,10 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import Severity from '../../../../base/common/severity.js'; import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; -import { INotificationService, INotificationActions } from '../../../../platform/notification/common/notification.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; -import { IHostService } from '../../../../platform/host/browser/host.js'; +import { IHostService } from '../../../services/host/browser/host.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { URI } from '../../../../base/common/uri.js'; import { IAction } from '../../../../base/common/actions.js'; @@ -35,7 +35,7 @@ class CortexideUpdateNotificationContribution extends Disposable implements IWor super(); // Wait for the window to have focus before showing the notification - this._hostService.hadLastFocus().then(async hadLastFocus => { + this._hostService.hadLastFocus().then(async (hadLastFocus: boolean) => { if (!hadLastFocus) { return; } @@ -68,8 +68,8 @@ class CortexideUpdateNotificationContribution extends Disposable implements IWor private _showUpdateNotification(version: string, isNewInstallation: boolean): void { const message = isNewInstallation - ? localize2('updateNotification.welcome', 'Welcome to {0} {1}!', this._productService.nameLong, version) - : localize2('updateNotification.updated', '{0} has been updated to {1}!', this._productService.nameLong, version); + ? localize2('updateNotification.welcome', 'Welcome to {0} {1}!', this._productService.nameLong, version).value + : localize2('updateNotification.updated', '{0} has been updated to {1}!', this._productService.nameLong, version).value; const primaryActions: IAction[] = []; @@ -77,9 +77,9 @@ class CortexideUpdateNotificationContribution extends Disposable implements IWor if (this._productService.releaseNotesUrl) { primaryActions.push({ id: 'cortexide.update.whatsNew', - label: localize2('updateNotification.whatsNew', 'What\'s New'), + label: localize2('updateNotification.whatsNew', 'What\'s New').value, enabled: true, - tooltip: localize2('updateNotification.whatsNewTooltip', 'View release notes'), + tooltip: localize2('updateNotification.whatsNewTooltip', 'View release notes').value, class: undefined, run: async () => { const uri = URI.parse(this._productService.releaseNotesUrl!); @@ -90,9 +90,9 @@ class CortexideUpdateNotificationContribution extends Disposable implements IWor // Fallback to GitHub releases if no release notes URL is configured primaryActions.push({ id: 'cortexide.update.whatsNew', - label: localize2('updateNotification.whatsNew', 'What\'s New'), + label: localize2('updateNotification.whatsNew', 'What\'s New').value, enabled: true, - tooltip: localize2('updateNotification.whatsNewTooltip', 'View release notes'), + tooltip: localize2('updateNotification.whatsNewTooltip', 'View release notes').value, class: undefined, run: async () => { const uri = URI.parse('https://github.com/opencortexide/cortexide/releases'); @@ -110,9 +110,9 @@ class CortexideUpdateNotificationContribution extends Disposable implements IWor secondary: [ { id: 'cortexide.update.dismiss', - label: localize2('updateNotification.dismiss', 'Dismiss'), + label: localize2('updateNotification.dismiss', 'Dismiss').value, enabled: true, - tooltip: localize2('updateNotification.dismissTooltip', 'Dismiss this notification'), + tooltip: localize2('updateNotification.dismissTooltip', 'Dismiss this notification').value, class: undefined, run: () => { // Mark this version as dismissed @@ -132,11 +132,17 @@ class CortexideUpdateNotificationContribution extends Disposable implements IWor // Auto-dismiss after 30 seconds if it's not a new installation if (!isNewInstallation) { - setTimeout(() => { - if (!notificationHandle.isDisposed) { + const timeoutId = setTimeout(() => { + try { notificationHandle.close(); + } catch (e) { + // Notification may have already been closed } }, 30000); + // Clean up timeout if notification is closed early + notificationHandle.onDidClose(() => { + clearTimeout(timeoutId); + }); } } } diff --git a/src/vs/workbench/contrib/cortexide/browser/react/src/void-onboarding/VoidOnboarding.tsx b/src/vs/workbench/contrib/cortexide/browser/react/src/void-onboarding/VoidOnboarding.tsx index 2955f63887c..28ad38d19c7 100644 --- a/src/vs/workbench/contrib/cortexide/browser/react/src/void-onboarding/VoidOnboarding.tsx +++ b/src/vs/workbench/contrib/cortexide/browser/react/src/void-onboarding/VoidOnboarding.tsx @@ -660,7 +660,7 @@ const VoidOnboardingContent = () => { // can be md const detailedDescOfWantToUseOption: { [wantToUseOption in WantToUseOption]: string } = { smart: "Most intelligent and best for agent mode.", - private: "Private-hosted so your data never leaves your computer or network. [Email us](mailto:founders@voideditor.com) for help setting up at your company.", + private: "Private-hosted so your data never leaves your computer or network. [Contact us](https://github.com/opencortexide/cortexide/issues/new) for help setting up at your company.", cheap: "Use great deals like Gemini 2.5 Pro, or self-host a model with Ollama or vLLM for free.", all: "", } diff --git a/src/vs/workbench/contrib/cortexide/common/cortexideSettingsTypes.ts b/src/vs/workbench/contrib/cortexide/common/cortexideSettingsTypes.ts index 0cc90578b2a..fcfb3c2dcd1 100644 --- a/src/vs/workbench/contrib/cortexide/common/cortexideSettingsTypes.ts +++ b/src/vs/workbench/contrib/cortexide/common/cortexideSettingsTypes.ts @@ -121,7 +121,7 @@ export const subTextMdOfProviderName = (providerName: ProviderName): string => { if (providerName === 'xAI') return 'Get your [API Key here](https://console.x.ai).' if (providerName === 'mistral') return 'Get your [API Key here](https://console.mistral.ai/api-keys).' if (providerName === 'openAICompatible') return `Use any provider that's OpenAI-compatible (use this for llama.cpp and more).` - if (providerName === 'googleVertex') return 'You must authenticate before using Vertex with Void. Read more about endpoints [here](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library), and regions [here](https://cloud.google.com/vertex-ai/docs/general/locations#available-regions).' + if (providerName === 'googleVertex') return 'You must authenticate before using Vertex with CortexIDE. Read more about endpoints [here](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/call-vertex-using-openai-library), and regions [here](https://cloud.google.com/vertex-ai/docs/general/locations#available-regions).' if (providerName === 'microsoftAzure') return 'Read more about endpoints [here](https://learn.microsoft.com/en-us/rest/api/aifoundry/model-inference/get-chat-completions/get-chat-completions?view=rest-aifoundry-model-inference-2024-05-01-preview&tabs=HTTP), and get your API key [here](https://learn.microsoft.com/en-us/azure/search/search-security-api-keys?tabs=rest-use%2Cportal-find%2Cportal-query#find-existing-keys).' if (providerName === 'awsBedrock') return 'Connect via a LiteLLM proxy or the AWS [Bedrock-Access-Gateway](https://github.com/aws-samples/bedrock-access-gateway). LiteLLM Bedrock setup docs are [here](https://docs.litellm.ai/docs/providers/bedrock).' if (providerName === 'ollama') return 'Read more about custom [Endpoints here](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-expose-ollama-on-my-network).' diff --git a/src/vs/workbench/contrib/cortexide/electron-main/cortexideUpdateMainService.ts b/src/vs/workbench/contrib/cortexide/electron-main/cortexideUpdateMainService.ts index 57479582bb3..9add9f62f66 100644 --- a/src/vs/workbench/contrib/cortexide/electron-main/cortexideUpdateMainService.ts +++ b/src/vs/workbench/contrib/cortexide/electron-main/cortexideUpdateMainService.ts @@ -95,7 +95,7 @@ export class CortexideMainUpdateService extends Disposable implements ICortexide private async _manualCheckGHTagIfDisabled(explicit: boolean): Promise { try { - const response = await fetch('https://api.github.com/repos/cortexide/cortexide/releases/latest'); + const response = await fetch('https://api.github.com/repos/opencortexide/cortexide/releases/latest'); const data = await response.json(); const version = data.tag_name; From 9d8a7cba8893c2cffb4c1913a8c0a63a65118b84 Mon Sep 17 00:00:00 2001 From: Tajudeen Date: Fri, 21 Nov 2025 20:17:27 +0000 Subject: [PATCH 03/44] Fix module resolution errors for Electron imports and workbench modules - Fix Electron import in src/main.ts: use default import and destructure to resolve ESM compatibility issues - Fix import map paths in workbench.ts: - Add correct paths for languageDetectionWorkerService, search, and terminal modules - Add aliases for old paths to maintain backward compatibility - Fix source import paths in React services.tsx: - Update languageDetectionWorkerService import to include workbench/ prefix - Update search service import to include workbench/ prefix - Add import map aliases for terminal module to handle both correct and legacy paths These fixes resolve ERR_FILE_NOT_FOUND and module specifier resolution errors when loading the workbench. --- build/lib/extensions.js.patched | 1 + build/lib/extensions.ts | 212 +- src/main.ts | 4 +- .../electron-browser/workbench/workbench.ts | 153 +- .../cortexide/browser/chatThreadService.ts | 2680 +++++++++-------- .../browser/convertToLLMMessageService.ts | 715 ++--- .../cortexide/browser/quickEditActions.ts | 253 +- .../src/quick-edit-tsx/QuickEditChat.tsx | 26 +- .../react/src/sidebar-tsx/ChatTabsBar.tsx | 149 + .../react/src/sidebar-tsx/SidebarChat.tsx | 282 +- .../src/sidebar-tsx/SidebarThreadSelector.tsx | 22 +- .../browser/react/src/util/services.tsx | 4 +- .../src/void-onboarding/VoidOnboarding.tsx | 121 +- .../cortexide/browser/repoIndexerService.ts | 119 +- .../cortexide/browser/sidebarActions.ts | 175 +- .../contrib/cortexide/browser/toolsService.ts | 910 ++++-- .../common/helpers/extractCodeFromResult.ts | 270 +- .../contrib/cortexide/common/mcpService.ts | 127 +- .../cortexide/common/mcpServiceTypes.ts | 48 +- .../cortexide/common/modelCapabilities.ts | 389 +-- .../contrib/cortexide/common/modelRouter.ts | 173 +- .../cortexide/common/sendLLMMessageService.ts | 137 +- .../cortexide/electron-main/mcpChannel.ts | 275 +- 23 files changed, 4133 insertions(+), 3112 deletions(-) create mode 100644 build/lib/extensions.js.patched create mode 100644 src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/ChatTabsBar.tsx diff --git a/build/lib/extensions.js.patched b/build/lib/extensions.js.patched new file mode 100644 index 00000000000..b1759f75ad7 --- /dev/null +++ b/build/lib/extensions.js.patched @@ -0,0 +1 @@ +utf8 \ No newline at end of file diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 4779ddba03a..57b66f48cec 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -9,6 +9,7 @@ import cp from 'child_process'; import glob from 'glob'; import gulp from 'gulp'; import path from 'path'; +import { pathToFileURL } from 'url'; import crypto from 'crypto'; import { Stream } from 'stream'; import File from 'vinyl'; @@ -93,116 +94,143 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string, const webpack = require('webpack'); const webpackGulp = require('webpack-stream'); const result = es.through(); - - const packagedDependencies: string[] = []; + let packagedDependencies: string[] = []; const packageJsonConfig = require(path.join(extensionPath, 'package.json')); - if (packageJsonConfig.dependencies) { - const webpackRootConfig = require(path.join(extensionPath, webpackConfigFileName)).default; - for (const key in webpackRootConfig.externals) { - if (key in packageJsonConfig.dependencies) { - packagedDependencies.push(key); + + (async () => { + try { + if (packageJsonConfig.dependencies) { + const webpackRootConfig = await loadWebpackConfigModule(path.join(extensionPath, webpackConfigFileName)); + if (webpackRootConfig && webpackRootConfig.externals) { + for (const key in webpackRootConfig.externals) { + if (key in packageJsonConfig.dependencies) { + packagedDependencies.push(key); + } + } + } } - } - } - // TODO: add prune support based on packagedDependencies to vsce.PackageManager.Npm similar - // to vsce.PackageManager.Yarn. - // A static analysis showed there are no webpack externals that are dependencies of the current - // local extensions so we can use the vsce.PackageManager.None config to ignore dependencies list - // as a temporary workaround. - vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.None, packagedDependencies }).then(fileNames => { - const files = fileNames - .map(fileName => path.join(extensionPath, fileName)) - .map(filePath => new File({ - path: filePath, - stat: fs.statSync(filePath), - base: extensionPath, - contents: fs.createReadStream(filePath) - })); + // TODO: add prune support based on packagedDependencies to vsce.PackageManager.Npm similar + // to vsce.PackageManager.Yarn. + // A static analysis showed there are no webpack externals that are dependencies of the current + // local extensions so we can use the vsce.PackageManager.None config to ignore dependencies list + // as a temporary workaround. + const fileNames = await vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.None, packagedDependencies }); + const files = fileNames + .map(fileName => path.join(extensionPath, fileName)) + .map(filePath => new File({ + path: filePath, + stat: fs.statSync(filePath), + base: extensionPath, + contents: fs.createReadStream(filePath) + })); - // check for a webpack configuration files, then invoke webpack - // and merge its output with the files stream. - const webpackConfigLocations = (glob.sync( - path.join(extensionPath, '**', webpackConfigFileName), - { ignore: ['**/node_modules'] } - )); + // check for a webpack configuration files, then invoke webpack + // and merge its output with the files stream. + const webpackConfigLocations = (glob.sync( + path.join(extensionPath, '**', webpackConfigFileName), + { ignore: ['**/node_modules'] } + )); - const webpackStreams = webpackConfigLocations.flatMap(webpackConfigPath => { + const webpackStreamGroups = await Promise.all(webpackConfigLocations.map(async webpackConfigPath => { - const webpackDone = (err: any, stats: any) => { - fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); - if (err) { - result.emit('error', err); - } - const { compilation } = stats; - if (compilation.errors.length > 0) { - result.emit('error', compilation.errors.join('\n')); - } - if (compilation.warnings.length > 0) { - result.emit('error', compilation.warnings.join('\n')); - } - }; - - const exportedConfig = require(webpackConfigPath).default; - return (Array.isArray(exportedConfig) ? exportedConfig : [exportedConfig]).map(config => { - const webpackConfig = { - ...config, - ...{ mode: 'production' } + const webpackDone = (err: any, stats: any) => { + fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); + if (err) { + result.emit('error', err); + } + const { compilation } = stats; + if (compilation.errors.length > 0) { + result.emit('error', compilation.errors.join('\n')); + } + if (compilation.warnings.length > 0) { + result.emit('error', compilation.warnings.join('\n')); + } }; - if (disableMangle) { - if (Array.isArray(config.module.rules)) { - for (const rule of config.module.rules) { - if (Array.isArray(rule.use)) { - for (const use of rule.use) { - if (String(use.loader).endsWith('mangle-loader.js')) { - use.options.disabled = true; + + const exportedConfig = await loadWebpackConfigModule(webpackConfigPath); + const configs = Array.isArray(exportedConfig) ? exportedConfig : [exportedConfig]; + return configs.map(config => { + const webpackConfig = { + ...config, + ...{ mode: 'production' } + }; + if (disableMangle) { + if (Array.isArray(config.module.rules)) { + for (const rule of config.module.rules) { + if (Array.isArray(rule.use)) { + for (const use of rule.use) { + if (String(use.loader).endsWith('mangle-loader.js')) { + use.options.disabled = true; + } } } } } } - } - const relativeOutputPath = path.relative(extensionPath, webpackConfig.output.path); - - return webpackGulp(webpackConfig, webpack, webpackDone) - .pipe(es.through(function (data) { - data.stat = data.stat || {}; - data.base = extensionPath; - this.emit('data', data); - })) - .pipe(es.through(function (data: File) { - // source map handling: - // * rewrite sourceMappingURL - // * save to disk so that upload-task picks this up - if (path.extname(data.basename) === '.js') { - const contents = (data.contents).toString('utf8'); - data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { - return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; - }), 'utf8'); - } + const relativeOutputPath = path.relative(extensionPath, webpackConfig.output.path); + + return webpackGulp(webpackConfig, webpack, webpackDone) + .pipe(es.through(function (data) { + data.stat = data.stat || {}; + data.base = extensionPath; + this.emit('data', data); + })) + .pipe(es.through(function (data: File) { + // source map handling: + // * rewrite sourceMappingURL + // * save to disk so that upload-task picks this up + if (path.extname(data.basename) === '.js') { + const contents = (data.contents).toString('utf8'); + data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { + return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; + }), 'utf8'); + } - this.emit('data', data); - })); - }); - }); + this.emit('data', data); + })); + }); + })); - es.merge(...webpackStreams, es.readArray(files)) - // .pipe(es.through(function (data) { - // // debug - // console.log('out', data.path, data.contents.length); - // this.emit('data', data); - // })) - .pipe(result); - - }).catch(err => { - console.error(extensionPath); - console.error(packagedDependencies); - result.emit('error', err); - }); + const webpackStreams = ([]).concat(...webpackStreamGroups); + + es.merge(...webpackStreams, es.readArray(files)) + // .pipe(es.through(function (data) { + // // debug + // console.log('out', data.path, data.contents.length); + // this.emit('data', data); + // })) + .pipe(result); + } catch (err) { + console.error(extensionPath); + console.error(packagedDependencies); + result.emit('error', err); + } + })(); return result.pipe(createStatsStream(path.basename(extensionPath))); } +async function loadWebpackConfigModule(webpackConfigPath: string): Promise { + let resolvedPath = webpackConfigPath; + if (resolvedPath.endsWith('.js')) { + const mjsPath = resolvedPath.replace(/\.js$/, '.mjs'); + try { + const srcStat = fs.statSync(resolvedPath); + const destStat = fs.existsSync(mjsPath) ? fs.statSync(mjsPath) : undefined; + if (!destStat || srcStat.mtimeMs > destStat.mtimeMs) { + fs.copyFileSync(resolvedPath, mjsPath); + } + } catch (error) { + // ignore copy errors, fallback to original path + } + resolvedPath = mjsPath; + } + + const module = await import(pathToFileURL(path.resolve(resolvedPath)).href); + return (module && Object.prototype.hasOwnProperty.call(module, 'default')) ? module.default : module; +} + function fromLocalNormal(extensionPath: string): Stream { const vsce = require('@vscode/vsce') as typeof import('@vscode/vsce'); const result = es.through(); diff --git a/src/main.ts b/src/main.ts index 7b7e1da509e..9732b38fc3f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,7 @@ import * as os from 'node:os'; import { performance } from 'node:perf_hooks'; import { configurePortable } from './bootstrap-node.js'; import { bootstrapESM } from './bootstrap-esm.js'; -import { app, protocol, crashReporter, Menu, contentTracing } from 'electron'; +import electron from 'electron'; import minimist from 'minimist'; import { product } from './bootstrap-meta.js'; import { parse } from './vs/base/common/jsonc.js'; @@ -20,6 +20,8 @@ import { getUNCHost, addUNCHostToAllowlist } from './vs/base/node/unc.js'; import { INLSConfiguration } from './vs/nls.js'; import { NativeParsedArgs } from './vs/platform/environment/common/argv.js'; +const { app, protocol, crashReporter, Menu, contentTracing } = electron; + perf.mark('code/didStartMain'); perf.mark('code/willLoadMainBundle', { diff --git a/src/vs/code/electron-browser/workbench/workbench.ts b/src/vs/code/electron-browser/workbench/workbench.ts index 7d6c8fac0c7..616ee28cce2 100644 --- a/src/vs/code/electron-browser/workbench/workbench.ts +++ b/src/vs/code/electron-browser/workbench/workbench.ts @@ -449,6 +449,8 @@ // DEV: a blob URL that loads the CSS via a dynamic @import-rule. // DEV --------------------------------------------------------------------------------------- + const importMap: { imports: Record } = { imports: {} }; + if (Array.isArray(configuration.cssModules) && configuration.cssModules.length > 0) { performance.mark('code/willAddCssLoader'); @@ -461,7 +463,6 @@ window.document.head.appendChild(link); }; - const importMap: { imports: Record } = { imports: {} }; for (const cssModule of configuration.cssModules) { const cssUrl = new URL(cssModule, baseUrl).href; const jsSrc = `globalThis._VSCODE_CSS_LOAD('${cssUrl}');\n`; @@ -469,6 +470,154 @@ importMap.imports[cssUrl] = URL.createObjectURL(blob); } + performance.mark('code/didAddCssLoader'); + } + + // Add import maps for React bundle modules to fix vscode-file:// protocol relative import issues + // These modules are imported by React bundle chunks using absolute paths (vs/workbench/...) that need import maps + const reactBundleModules = [ + 'vs/workbench/contrib/cortexide/browser/actionIDs.js', + 'vs/workbench/contrib/cortexide/browser/cortexideSettingsPane.js', + 'vs/workbench/contrib/cortexide/browser/terminalToolService.js', + 'vs/workbench/contrib/cortexide/common/cortexideSettingsTypes.js', + 'vs/workbench/contrib/cortexide/common/cortexideSettingsService.js', + 'vs/workbench/contrib/cortexide/common/sendLLMMessageService.js', + 'vs/workbench/contrib/cortexide/common/refreshModelService.js', + 'vs/workbench/contrib/cortexide/common/metricsService.js', + 'vs/workbench/contrib/cortexide/common/cortexideModelService.js', + 'vs/workbench/contrib/cortexide/common/mcpService.js', + 'vs/workbench/contrib/cortexide/common/storageKeys.js', + 'vs/workbench/contrib/cortexide/common/secretDetectionService.js', + 'vs/workbench/contrib/cortexide/common/helpers/languageHelpers.js', + 'vs/workbench/contrib/cortexide/common/helpers/util.js', + 'vs/workbench/contrib/cortexide/common/helpers/extractCodeFromResult.js', + 'vs/workbench/contrib/cortexide/common/sendLLMMessageTypes.js', + 'vs/workbench/contrib/cortexide/common/modelCapabilities.js', + 'vs/workbench/contrib/cortexide/common/toolsServiceTypes.js', + 'vs/workbench/contrib/cortexide/common/prompt/prompts.js', + 'vs/workbench/contrib/cortexide/common/mcpServiceTypes.js', + 'vs/workbench/contrib/cortexide/common/pdfService.js', + 'vs/workbench/contrib/cortexide/common/helpers/systemInfo.js', + ]; + + // Also add common VS Code modules that might be imported + const commonVSCodeModules = [ + 'vs/base/common/uri.js', + 'vs/base/common/path.js', + 'vs/base/common/severity.js', + 'vs/base/common/lifecycle.js', + 'vs/base/common/errorMessage.js', + 'vs/base/browser/ui/inputbox/inputBox.js', + 'vs/base/browser/ui/selectBox/selectBox.js', + 'vs/base/browser/ui/toggle/toggle.js', + 'vs/base/common/network.js', + 'vs/platform/theme/browser/defaultStyles.js', + 'vs/platform/theme/common/colorUtils.js', + 'vs/platform/theme/common/colorRegistry.js', + 'vs/platform/theme/common/theme.js', + 'vs/platform/storage/common/storage.js', + 'vs/editor/common/editorCommon.js', + 'vs/editor/browser/widget/codeEditor/codeEditorWidget.js', + 'vs/editor/browser/widget/diffEditor/diffEditorWidget.js', + ]; + + // Add React bundle chunk files to import maps (in case they're imported with absolute paths) + const reactChunkModules = [ + 'vs/workbench/contrib/cortexide/browser/react/out/chunk-RM77YOHK.js', + 'vs/workbench/contrib/cortexide/browser/react/out/chunk-RJP66NWB.js', + 'vs/workbench/contrib/cortexide/browser/react/out/chunk-PT4A2IRQ.js', + 'vs/workbench/contrib/cortexide/browser/react/out/chunk-SWVXQVDT.js', + 'vs/workbench/contrib/cortexide/browser/react/out/chunk-JSBRDJBE.js', + 'vs/workbench/contrib/cortexide/browser/react/out/chunk-6FX43ENS.js', + ]; + + const additionalReactDeps = [ + 'vs/editor/browser/services/codeEditorService.js', + 'vs/editor/browser/widget/diffEditor/diffEditorWidget.js', + 'vs/editor/browser/widget/codeEditor/codeEditorWidget.js', + 'vs/editor/common/editorCommon.js', + 'vs/editor/common/languages/language.js', + 'vs/editor/common/languages/languageConfigurationRegistry.js', + 'vs/editor/common/services/languageFeatures.js', + 'vs/editor/common/services/model.js', + 'vs/platform/accessibility/common/accessibility.js', + 'vs/platform/clipboard/common/clipboardService.js', + 'vs/platform/commands/common/commands.js', + 'vs/platform/configuration/common/configuration.js', + 'vs/platform/contextkey/common/contextkey.js', + 'vs/platform/contextview/browser/contextView.js', + 'vs/platform/environment/common/environment.js', + 'vs/platform/extensionManagement/common/extensionManagement.js', + 'vs/platform/files/common/files.js', + 'vs/platform/hover/browser/hover.js', + 'vs/platform/instantiation/common/instantiation.js', + 'vs/platform/keybinding/common/keybinding.js', + 'vs/platform/native/common/native.js', + 'vs/platform/notification/common/notification.js', + 'vs/platform/workspace/common/workspace.js', + 'vs/platform/theme/common/themeService.js', + 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService.js', + 'vs/workbench/services/search/common/search.js', + 'vs/workbench/contrib/cortexide/browser/common/cortexideSettingsTypes.js', + 'vs/workbench/contrib/cortexide/browser/extensionTransferService.js', + 'vs/workbench/contrib/cortexide/browser/terminalToolService.js', + 'vs/workbench/contrib/cortexide/chatThreadService.js', + 'vs/workbench/contrib/cortexide/common/cortexideModelService.js', + 'vs/workbench/contrib/cortexide/common/cortexideSettingsService.js', + 'vs/workbench/contrib/cortexide/common/helpers/extractCodeFromResult.js', + 'vs/workbench/contrib/cortexide/common/helpers/languageHelpers.js', + 'vs/workbench/contrib/cortexide/common/helpers/systemInfo.js', + 'vs/workbench/contrib/cortexide/common/helpers/util.js', + 'vs/workbench/contrib/cortexide/common/mcpService.js', + 'vs/workbench/contrib/cortexide/common/mcpServiceTypes.js', + 'vs/workbench/contrib/cortexide/common/metricsService.js', + 'vs/workbench/contrib/cortexide/common/modelCapabilities.js', + 'vs/workbench/contrib/cortexide/common/pdfService.js', + 'vs/workbench/contrib/cortexide/common/prompt/prompts.js', + 'vs/workbench/contrib/cortexide/common/refreshModelService.js', + 'vs/workbench/contrib/cortexide/common/secretDetectionService.js', + 'vs/workbench/contrib/cortexide/common/sendLLMMessageService.js', + 'vs/workbench/contrib/cortexide/common/sendLLMMessageTypes.js', + 'vs/workbench/contrib/cortexide/common/storageKeys.js', + 'vs/workbench/contrib/cortexide/common/toolsServiceTypes.js', + 'vs/workbench/contrib/cortexide/convertToLLMMessageService.js', + 'vs/workbench/contrib/cortexide/cortexideCommandBarService.js', + 'vs/workbench/contrib/cortexide/editCodeServiceInterface.js', + 'vs/workbench/contrib/cortexide/repoIndexerService.js', + 'vs/workbench/contrib/cortexide/toolsService.js', + 'vs/workbench/contrib/files/browser/files.js', + 'vs/workbench/services/path/common/pathService.js', + 'vs/workbench/contrib/terminal/browser/terminal.js', + ]; + + for (const module of [...reactBundleModules, ...commonVSCodeModules, ...reactChunkModules, ...additionalReactDeps]) { + const moduleUrl = new URL(module, baseUrl).href; + importMap.imports[module] = moduleUrl; + } + + // Provide aliases for modules that might be referenced without the /browser/ segment + const browserModuleAliases: Array<[string, string]> = [ + ['vs/workbench/contrib/cortexide/actionIDs.js', 'vs/workbench/contrib/cortexide/browser/actionIDs.js'], + ['vs/workbench/contrib/cortexide/cortexideSettingsPane.js', 'vs/workbench/contrib/cortexide/browser/cortexideSettingsPane.js'], + ['vs/workbench/contrib/cortexide/terminalToolService.js', 'vs/workbench/contrib/cortexide/browser/terminalToolService.js'], + ['vs/workbench/contrib/cortexide/chatThreadService.js', 'vs/workbench/contrib/cortexide/browser/chatThreadService.js'], + ['vs/workbench/contrib/cortexide/convertToLLMMessageService.js', 'vs/workbench/contrib/cortexide/browser/convertToLLMMessageService.js'], + ['vs/workbench/contrib/cortexide/cortexideCommandBarService.js', 'vs/workbench/contrib/cortexide/browser/cortexideCommandBarService.js'], + ['vs/workbench/contrib/cortexide/editCodeServiceInterface.js', 'vs/workbench/contrib/cortexide/browser/editCodeServiceInterface.js'], + ['vs/workbench/contrib/cortexide/repoIndexerService.js', 'vs/workbench/contrib/cortexide/browser/repoIndexerService.js'], + ['vs/workbench/contrib/cortexide/toolsService.js', 'vs/workbench/contrib/cortexide/browser/toolsService.js'], + ['vs/workbench/contrib/cortexide/extensionTransferService.js', 'vs/workbench/contrib/cortexide/browser/extensionTransferService.js'], + ['vs/workbench/contrib/cortexide/toolsService.js', 'vs/workbench/contrib/cortexide/browser/toolsService.js'], + // Aliases for old paths that might still be in bundled code + ['vs/services/languageDetection/common/languageDetectionWorkerService.js', 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService.js'], + ['vs/services/search/common/search.js', 'vs/workbench/services/search/common/search.js'], + ['vs/workbench/terminal/browser/terminal.js', 'vs/workbench/contrib/terminal/browser/terminal.js'], + ]; + for (const [alias, target] of browserModuleAliases) { + importMap.imports[alias] = new URL(target, baseUrl).href; + } + + if (Object.keys(importMap.imports).length > 0) { const ttp = window.trustedTypes?.createPolicy('vscode-bootstrapImportMap', { createScript(value) { return value; }, }); const importMapSrc = JSON.stringify(importMap, undefined, 2); const importMapScript = document.createElement('script'); @@ -477,8 +626,6 @@ // @ts-expect-error importMapScript.textContent = ttp?.createScript(importMapSrc) ?? importMapSrc; window.document.head.appendChild(importMapScript); - - performance.mark('code/didAddCssLoader'); } } diff --git a/src/vs/workbench/contrib/cortexide/browser/chatThreadService.ts b/src/vs/workbench/contrib/cortexide/browser/chatThreadService.ts index ca61cffacf3..eb43f5a8def 100644 --- a/src/vs/workbench/contrib/cortexide/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/cortexide/browser/chatThreadService.ts @@ -1,7 +1,7 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import { Disposable } from '../../../../base/common/lifecycle.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; @@ -14,7 +14,7 @@ import { ILLMMessageService } from '../common/sendLLMMessageService.js'; import { chat_userMessageContent, isABuiltinToolName } from '../common/prompt/prompts.js'; import { AnthropicReasoning, getErrorMessage, RawToolCallObj, RawToolParamsObj } from '../common/sendLLMMessageTypes.js'; import { generateUuid } from '../../../../base/common/uuid.js'; -import { FeatureName, ModelSelection, ModelSelectionOptions, ProviderName } from '../common/cortexideSettingsTypes.js'; +import { FeatureName, ModelSelection, ModelSelectionOptions, ProviderName, isAutoModelSelection } from '../common/cortexideSettingsTypes.js'; import { ICortexideSettingsService } from '../common/cortexideSettingsService.js'; import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, BuiltinToolResultType, ToolCallParams, ToolName, ToolResult } from '../common/toolsServiceTypes.js'; import { IToolsService } from './toolsService.js'; @@ -52,38 +52,38 @@ import { IAuditLogService } from '../common/auditLogService.js'; // related to retrying when LLM message has error // Optimized retry logic: faster initial retry, exponential backoff -const CHAT_RETRIES = 3 -const INITIAL_RETRY_DELAY = 1000 // Start with 1s for faster recovery -const MAX_RETRY_DELAY = 5000 // Cap at 5s -const MAX_AGENT_LOOP_ITERATIONS = 20 // Maximum iterations to prevent infinite loops -const MAX_FILES_READ_PER_QUERY = 10 // Maximum files to read in a single query to prevent excessive reads +const CHAT_RETRIES = 3; +const INITIAL_RETRY_DELAY = 1000; // Start with 1s for faster recovery +const MAX_RETRY_DELAY = 5000; // Cap at 5s +const MAX_AGENT_LOOP_ITERATIONS = 20; // Maximum iterations to prevent infinite loops +const MAX_FILES_READ_PER_QUERY = 10; // Maximum files to read in a single query to prevent excessive reads const findStagingSelectionIndex = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem): number | null => { - if (!currentSelections) return null + if (!currentSelections) { return null; } for (let i = 0; i < currentSelections.length; i += 1) { - const s = currentSelections[i] + const s = currentSelections[i]; - if (s.uri.fsPath !== newSelection.uri.fsPath) continue + if (s.uri.fsPath !== newSelection.uri.fsPath) { continue; } if (s.type === 'File' && newSelection.type === 'File') { - return i + return i; } if (s.type === 'CodeSelection' && newSelection.type === 'CodeSelection') { - if (s.uri.fsPath !== newSelection.uri.fsPath) continue + // URI already checked above, no need to check again // if there's any collision return true - const [oldStart, oldEnd] = s.range - const [newStart, newEnd] = newSelection.range - if (oldStart !== newStart || oldEnd !== newEnd) continue - return i + const [oldStart, oldEnd] = s.range; + const [newStart, newEnd] = newSelection.range; + if (oldStart !== newStart || oldEnd !== newEnd) { continue; } + return i; } if (s.type === 'Folder' && newSelection.type === 'Folder') { - return i + return i; } } - return null -} + return null; +}; /* @@ -108,19 +108,19 @@ A checkpoint appears before every LLM message, and before every user message (be */ -type UserMessageType = ChatMessage & { role: 'user' } -type UserMessageState = UserMessageType['state'] +type UserMessageType = ChatMessage & { role: 'user' }; +type UserMessageState = UserMessageType['state']; const defaultMessageState: UserMessageState = { stagingSelections: [], isBeingEdited: false, -} +}; // a 'thread' means a chat message history type WhenMounted = { textAreaRef: { current: HTMLTextAreaElement | null }; // the textarea that this thread has, gets set in SidebarChat scrollToBottom: () => void; -} +}; @@ -141,30 +141,31 @@ export type ThreadType = { linksOfMessageIdx: { // eg. link = linksOfMessageIdx[4]['RangeFunction'] [messageIdx: number]: { - [codespanName: string]: CodespanLocationLink - } - } + [codespanName: string]: CodespanLocationLink; + }; + }; mountedInfo?: { - whenMounted: Promise - _whenMountedResolver: (res: WhenMounted) => void + whenMounted: Promise; + _whenMountedResolver: (res: WhenMounted) => void; mountedIsResolvedRef: { current: boolean }; - } + }; }; -} +}; type ChatThreads = { [id: string]: undefined | ThreadType; -} +}; export type ThreadsState = { allThreads: ChatThreads; currentThreadId: string; // intended for internal use only -} + openTabs: string[]; // array of thread IDs that are open in tabs +}; export type IsRunningType = | 'LLM' // the LLM is currently streaming @@ -172,12 +173,12 @@ export type IsRunningType = | 'awaiting_user' // awaiting user call | 'preparing' // preparing request (model selection, validation, etc.) | 'idle' // nothing is running now, but the chat should still appear like it's going (used in-between calls) - | undefined + | undefined; export type ThreadStreamState = { [threadId: string]: undefined | { isRunning: undefined; - error?: { message: string, fullError: Error | null, }; + error?: { message: string; fullError: Error | null }; llmInfo?: undefined; toolInfo?: undefined; interrupt?: undefined; @@ -226,11 +227,11 @@ export type ThreadStreamState = { llmInfo?: undefined; toolInfo?: undefined; interrupt: 'not_needed' | Promise<() => void>; // calling this should have no effect on state - would be too confusing. it just cancels the tool - } -} + }; +}; const newThreadObject = () => { - const now = new Date().toISOString() + const now = new Date().toISOString(); return { id: generateUuid(), createdAt: now, @@ -243,8 +244,8 @@ const newThreadObject = () => { linksOfMessageIdx: {}, }, filesWithUserChanges: new Set() - } satisfies ThreadType -} + } satisfies ThreadType; +}; @@ -258,7 +259,7 @@ export interface IChatThreadService { readonly streamState: ThreadStreamState; // not persistent onDidChangeCurrentThread: Event; - onDidChangeStreamState: Event<{ threadId: string }> + onDidChangeStreamState: Event<{ threadId: string }>; getCurrentThread(): ThreadType; openNewThread(): void; @@ -268,12 +269,18 @@ export interface IChatThreadService { deleteThread(threadId: string): void; duplicateThread(threadId: string): void; + // tab management + openTab(threadId: string): void; + closeTab(threadId: string): void; + switchToTab(threadId: string): void; + getOpenTabs(): string[]; + // exposed getters/setters // these all apply to current thread - getCurrentMessageState: (messageIdx: number) => UserMessageState - setCurrentMessageState: (messageIdx: number, newState: Partial) => void - getCurrentThreadState: () => ThreadType['state'] - setCurrentThreadState: (newState: Partial) => void + getCurrentMessageState: (messageIdx: number) => UserMessageState; + setCurrentMessageState: (messageIdx: number, newState: Partial) => void; + getCurrentThreadState: () => ThreadType['state']; + setCurrentThreadState: (newState: Partial) => void; // you can edit multiple messages - the one you're currently editing is "focused", and we add items to that one when you press cmd+L. getCurrentFocusedMessageIdx(): number | undefined; @@ -291,44 +298,44 @@ export interface IChatThreadService { // closeCurrentStagingSelectionsInThread(): void; // codespan links (link to symbols in the markdown) - getCodespanLink(opts: { codespanStr: string, messageIdx: number, threadId: string }): CodespanLocationLink | undefined; - addCodespanLink(opts: { newLinkText: string, newLinkLocation: CodespanLocationLink, messageIdx: number, threadId: string }): void; - generateCodespanLink(opts: { codespanStr: string, threadId: string }): Promise; - getRelativeStr(uri: URI): string | undefined + getCodespanLink(opts: { codespanStr: string; messageIdx: number; threadId: string }): CodespanLocationLink | undefined; + addCodespanLink(opts: { newLinkText: string; newLinkLocation: CodespanLocationLink; messageIdx: number; threadId: string }): void; + generateCodespanLink(opts: { codespanStr: string; threadId: string }): Promise; + getRelativeStr(uri: URI): string | undefined; // entry pts abortRunning(threadId: string): Promise; dismissStreamError(threadId: string): void; // call to edit a message - editUserMessageAndStreamResponse({ userMessage, messageIdx, threadId }: { userMessage: string, messageIdx: number, threadId: string }): Promise; + editUserMessageAndStreamResponse({ userMessage, messageIdx, threadId }: { userMessage: string; messageIdx: number; threadId: string }): Promise; // call to add a message - addUserMessageAndStreamResponse({ userMessage, threadId, images, noPlan, displayContent }: { userMessage: string, threadId: string, images?: ChatImageAttachment[], noPlan?: boolean, displayContent?: string }): Promise; + addUserMessageAndStreamResponse({ userMessage, threadId, images, noPlan, displayContent }: { userMessage: string; threadId: string; images?: ChatImageAttachment[]; noPlan?: boolean; displayContent?: string }): Promise; // approve/reject approveLatestToolRequest(threadId: string): void; rejectLatestToolRequest(threadId: string): void; // jump to history - jumpToCheckpointBeforeMessageIdx(opts: { threadId: string, messageIdx: number, jumpToUserModified: boolean }): void; + jumpToCheckpointBeforeMessageIdx(opts: { threadId: string; messageIdx: number; jumpToUserModified: boolean }): void; // Plan management methods - approvePlan(opts: { threadId: string, messageIdx: number }): void; - rejectPlan(opts: { threadId: string, messageIdx: number }): void; - editPlan(opts: { threadId: string, messageIdx: number, updatedPlan: PlanMessage }): void; - toggleStepDisabled(opts: { threadId: string, messageIdx: number, stepNumber: number }): void; - reorderPlanSteps(opts: { threadId: string, messageIdx: number, newStepOrder: number[] }): void; + approvePlan(opts: { threadId: string; messageIdx: number }): void; + rejectPlan(opts: { threadId: string; messageIdx: number }): void; + editPlan(opts: { threadId: string; messageIdx: number; updatedPlan: PlanMessage }): void; + toggleStepDisabled(opts: { threadId: string; messageIdx: number; stepNumber: number }): void; + reorderPlanSteps(opts: { threadId: string; messageIdx: number; newStepOrder: number[] }): void; // Step execution control pauseAgentExecution(opts: { threadId: string }): Promise; resumeAgentExecution(opts: { threadId: string }): Promise; - retryStep(opts: { threadId: string, messageIdx: number, stepNumber: number }): Promise; - skipStep(opts: { threadId: string, messageIdx: number, stepNumber: number }): void; - rollbackToStep(opts: { threadId: string, messageIdx: number, stepNumber: number }): Promise; + retryStep(opts: { threadId: string; messageIdx: number; stepNumber: number }): Promise; + skipStep(opts: { threadId: string; messageIdx: number; stepNumber: number }): void; + rollbackToStep(opts: { threadId: string; messageIdx: number; stepNumber: number }): Promise; - focusCurrentChat: () => Promise - blurCurrentChat: () => Promise + focusCurrentChat: () => Promise; + blurCurrentChat: () => Promise; } export const IChatThreadService = createDecorator('voidChatThreadService'); @@ -342,8 +349,8 @@ class ChatThreadService extends Disposable implements IChatThreadService { private readonly _onDidChangeStreamState = new Emitter<{ threadId: string }>(); readonly onDidChangeStreamState: Event<{ threadId: string }> = this._onDidChangeStreamState.event; - readonly streamState: ThreadStreamState = {} - state: ThreadsState // allThreads is persisted, currentThread is not + readonly streamState: ThreadStreamState = {}; + state: ThreadsState; // allThreads is persisted, currentThread is not // used in checkpointing // private readonly _userModifiedFilesToCheckInCheckpoints = new LRUCache(50) @@ -351,16 +358,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { // Cache for file read results to prevent duplicate reads // Key: threadId -> cacheKey (uri.fsPath + startLine + endLine + pageNumber) -> cached result // Uses LRU eviction to prevent unbounded memory growth - private readonly _fileReadCache: Map> = new Map() + private readonly _fileReadCache: Map> = new Map(); // LRU tracking for file read cache (threadId -> ordered list of cache keys) - private readonly _fileReadCacheLRU: Map = new Map() - private static readonly MAX_FILE_READ_CACHE_ENTRIES_PER_THREAD = 100 // Limit cache size per thread + private readonly _fileReadCacheLRU: Map = new Map(); + private static readonly MAX_FILE_READ_CACHE_ENTRIES_PER_THREAD = 100; // Limit cache size per thread // Throttle stream state updates during streaming to reduce React re-renders // Use requestAnimationFrame to batch updates for better performance - private readonly _pendingStreamStateUpdates = new Map() - private _streamStateRafId: number | undefined + private readonly _pendingStreamStateUpdates = new Map(); + private _streamStateRafId: number | undefined; @@ -385,21 +392,22 @@ class ChatThreadService extends Disposable implements IChatThreadService { @ICommandService private readonly _commandService: ICommandService, @IAuditLogService private readonly _auditLogService: IAuditLogService, ) { - super() - this.state = { allThreads: {}, currentThreadId: null as unknown as string } // default state + super(); + this.state = { allThreads: {}, currentThreadId: null as unknown as string, openTabs: [] }; // default state // When set for a thread, the next call to _shouldGeneratePlan will return false and clear the flag - this._suppressPlanOnceByThread = {} + this._suppressPlanOnceByThread = {}; - const readThreads = this._readAllThreads() || {} + const readThreads = this._readAllThreads() || {}; - const allThreads = readThreads + const allThreads = readThreads; this.state = { allThreads: allThreads, currentThreadId: null as unknown as string, // gets set in startNewThread() - } + openTabs: [], // will be initialized when first thread is opened + }; // always be in a thread - this.openNewThread() + this.openNewThread(); // keep track of user-modified files @@ -407,38 +415,38 @@ class ChatThreadService extends Disposable implements IChatThreadService { } // If true for a thread, suppress plan generation once for the next user message - private _suppressPlanOnceByThread: Record + private _suppressPlanOnceByThread: Record; async focusCurrentChat() { - const threadId = this.state.currentThreadId - const thread = this.state.allThreads[threadId] - if (!thread) return - const s = await thread.state.mountedInfo?.whenMounted + const threadId = this.state.currentThreadId; + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } + const s = await thread.state.mountedInfo?.whenMounted; if (!this.isCurrentlyFocusingMessage()) { - s?.textAreaRef.current?.focus() + s?.textAreaRef.current?.focus(); } } async blurCurrentChat() { - const threadId = this.state.currentThreadId - const thread = this.state.allThreads[threadId] - if (!thread) return - const s = await thread.state.mountedInfo?.whenMounted + const threadId = this.state.currentThreadId; + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } + const s = await thread.state.mountedInfo?.whenMounted; if (!this.isCurrentlyFocusingMessage()) { - s?.textAreaRef.current?.blur() + s?.textAreaRef.current?.blur(); } } dangerousSetState = (newState: ThreadsState) => { - this.state = newState - this._onDidChangeCurrentThread.fire() - } + this.state = newState; + this._onDidChangeCurrentThread.fire(); + }; resetState = () => { - this.state = { allThreads: {}, currentThreadId: null as unknown as string } // see constructor - this.openNewThread() - this._onDidChangeCurrentThread.fire() - } + this.state = { allThreads: {}, currentThreadId: null as unknown as string, openTabs: [] }; // see constructor + this.openNewThread(); + this._onDidChangeCurrentThread.fire(); + }; // !!! this is important for properly restoring URIs and images from storage // should probably re-use code from void/src/vs/base/common/marshalling.ts instead. but this is simple enough @@ -468,7 +476,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { } else if (Array.isArray(value)) { // Handle case where it's already an array but not Uint8Array // Only convert if it looks like byte data (all numbers 0-255) - if (value.length > 0 && value.every((v: any) => typeof v === 'number' && v >= 0 && v <= 255)) { + if (value.length > 0 && value.every((v: unknown) => typeof v === 'number' && v >= 0 && v <= 255)) { return new Uint8Array(value as number[]); } } @@ -482,11 +490,11 @@ class ChatThreadService extends Disposable implements IChatThreadService { private _readAllThreads(): ChatThreads | null { const threadsStr = this._storageService.get(THREAD_STORAGE_KEY, StorageScope.APPLICATION); if (!threadsStr) { - return null + return null; } const threads = this._convertThreadDataFromStorage(threadsStr); - return threads + return threads; } private _storeAllThreads(threads: ChatThreads) { @@ -520,51 +528,50 @@ class ChatThreadService extends Disposable implements IChatThreadService { const newState = { ...this.state, ...state - } + }; - this.state = newState + this.state = newState; - this._onDidChangeCurrentThread.fire() + this._onDidChangeCurrentThread.fire(); // if we just switched to a thread, update its current stream state if it's not streaming to possibly streaming - const threadId = newState.currentThreadId - const streamState = this.streamState[threadId] + const threadId = newState.currentThreadId; + const streamState = this.streamState[threadId]; if (streamState?.isRunning === undefined && !streamState?.error) { // set streamState - const messages = newState.allThreads[threadId]?.messages - const lastMessage = messages && messages[messages.length - 1] + const messages = newState.allThreads[threadId]?.messages; + const lastMessage = messages && messages[messages.length - 1]; // if awaiting user but stream state doesn't indicate it (happens if restart Void) - if (lastMessage && lastMessage.role === 'tool' && lastMessage.type === 'tool_request') - this._setStreamState(threadId, { isRunning: 'awaiting_user', }) + if (lastMessage && lastMessage.role === 'tool' && lastMessage.type === 'tool_request') { this._setStreamState(threadId, { isRunning: 'awaiting_user', }); } // if running now but stream state doesn't indicate it (happens if restart Void), cancel that last tool if (lastMessage && lastMessage.role === 'tool' && lastMessage.type === 'running_now') { - this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', content: lastMessage.content, id: lastMessage.id, rawParams: lastMessage.rawParams, result: null, name: lastMessage.name, params: lastMessage.params, mcpServerName: lastMessage.mcpServerName }) + this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', content: lastMessage.content, id: lastMessage.id, rawParams: lastMessage.rawParams, result: null, name: lastMessage.name, params: lastMessage.params, mcpServerName: lastMessage.mcpServerName }); } } // if we did not just set the state to true, set mount info - if (doNotRefreshMountInfo) return + if (doNotRefreshMountInfo) { return; } - let whenMountedResolver: (w: WhenMounted) => void - const whenMountedPromise = new Promise((res) => whenMountedResolver = res) + let whenMountedResolver: (w: WhenMounted) => void; + const whenMountedPromise = new Promise((res) => whenMountedResolver = res); this._setThreadState(threadId, { mountedInfo: { whenMounted: whenMountedPromise, mountedIsResolvedRef: { current: false }, _whenMountedResolver: (w: WhenMounted) => { - whenMountedResolver(w) - const mountInfo = this.state.allThreads[threadId]?.state.mountedInfo - if (mountInfo) mountInfo.mountedIsResolvedRef.current = true + whenMountedResolver(w); + const mountInfo = this.state.allThreads[threadId]?.state.mountedInfo; + if (mountInfo) { mountInfo.mountedIsResolvedRef.current = true; } }, } - }, true) // do not trigger an update + }, true); // do not trigger an update @@ -572,31 +579,31 @@ class ChatThreadService extends Disposable implements IChatThreadService { private _setStreamState(threadId: string, state: ThreadStreamState[string]) { - this.streamState[threadId] = state + this.streamState[threadId] = state; // Throttle updates during streaming to reduce React re-render frequency // Batch updates using requestAnimationFrame for smoother performance - const isStreaming = state?.isRunning === 'LLM' + const isStreaming = state?.isRunning === 'LLM'; if (isStreaming) { // During streaming, batch updates using requestAnimationFrame - this._pendingStreamStateUpdates.set(threadId, state) + this._pendingStreamStateUpdates.set(threadId, state); if (this._streamStateRafId === undefined) { this._streamStateRafId = requestAnimationFrame(() => { // Fire all pending updates in a single batch for (const [tid] of this._pendingStreamStateUpdates) { - this._onDidChangeStreamState.fire({ threadId: tid }) + this._onDidChangeStreamState.fire({ threadId: tid }); } - this._pendingStreamStateUpdates.clear() - this._streamStateRafId = undefined - }) + this._pendingStreamStateUpdates.clear(); + this._streamStateRafId = undefined; + }); } } else { // For non-streaming updates (idle, error, etc.), fire immediately // Also clear any pending updates for this thread - this._pendingStreamStateUpdates.delete(threadId) - this._onDidChangeStreamState.fire({ threadId }) + this._pendingStreamStateUpdates.delete(threadId); + this._onDidChangeStreamState.fire({ threadId }); } } @@ -607,14 +614,14 @@ class ChatThreadService extends Disposable implements IChatThreadService { private _currentModelSelectionProps = () => { // these settings should not change throughout the loop (eg anthropic breaks if you change its thinking mode and it's using tools) - const featureName: FeatureName = 'Chat' - const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName] + const featureName: FeatureName = 'Chat'; + const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]; // Skip "auto" - it's not a real provider const modelSelectionOptions = modelSelection && !(modelSelection.providerName === 'auto' && modelSelection.modelName === 'auto') ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] - : undefined - return { modelSelection, modelSelectionOptions } - } + : undefined; + return { modelSelection, modelSelectionOptions }; + }; /** * Auto-select model based on task context @@ -625,24 +632,24 @@ class ChatThreadService extends Disposable implements IChatThreadService { images?: ChatImageAttachment[], pdfs?: ChatPDFAttachment[] ): Promise { - const featureName: FeatureName = 'Chat' - const userManualSelection = this._settingsService.state.modelSelectionOfFeature[featureName] + const featureName: FeatureName = 'Chat'; + const userManualSelection = this._settingsService.state.modelSelectionOfFeature[featureName]; // If user has a specific model selected (not "Auto"), respect it if (userManualSelection && !(userManualSelection.providerName === 'auto' && userManualSelection.modelName === 'auto')) { - return userManualSelection + return userManualSelection; } // Detect task type from message and attachments - const taskType = this._detectTaskType(userMessage, images, pdfs) - const hasImages = images && images.length > 0 - const hasPDFs = pdfs && pdfs.length > 0 - const hasCode = this._detectCodeInMessage(userMessage) + const taskType = this._detectTaskType(userMessage, images, pdfs); + const hasImages = images && images.length > 0; + const hasPDFs = pdfs && pdfs.length > 0; + const hasCode = this._detectCodeInMessage(userMessage); // Detect complexity indicators - const lowerMessage = userMessage.toLowerCase().trim() - const reasoningKeywords = ['explain why', 'analyze', 'compare and contrast', 'evaluate', 'critique', 'reasoning', 'logical', 'deduce', 'infer', 'conclusion', 'argument', 'thesis', 'hypothesis', 'theoretical', 'conceptual'] - const complexAnalysisKeywords = ['complex', 'sophisticated', 'nuanced', 'detailed analysis', 'deep understanding', 'comprehensive', 'thorough'] + const lowerMessage = userMessage.toLowerCase().trim(); + const reasoningKeywords = ['explain why', 'analyze', 'compare and contrast', 'evaluate', 'critique', 'reasoning', 'logical', 'deduce', 'infer', 'conclusion', 'argument', 'thesis', 'hypothesis', 'theoretical', 'conceptual']; + const complexAnalysisKeywords = ['complex', 'sophisticated', 'nuanced', 'detailed analysis', 'deep understanding', 'comprehensive', 'thorough']; // Codebase questions require complex reasoning (understanding structure, relationships, etc.) // Use the same detection logic as _detectTaskType for consistency @@ -652,27 +659,27 @@ class ChatThreadService extends Disposable implements IChatThreadService { /^what\s+(is|does|are)\s+(my|this|the)\s+(codebase|repo|project|code|app|application)/, /\bhow\s+many\s+(endpoint|endpoints|api|apis|route|routes|file|files|function|functions|class|classes|component|components|module|modules|service|services|controller|controllers)\b/i, /^(summarize|explain|describe|overview|analyze)\s+(my|this|the)\s+(codebase|repo|project|code)/, - ] - const codebaseIndicators = ['codebase', 'code base', 'repository', 'repo', 'project structure', 'architecture', 'endpoint', 'api', 'route'] - const questionStarters = ['what is', 'what does', 'how many', 'summarize', 'explain', 'describe', 'overview'] - const matchesPattern = codebaseQuestionPatterns.some(pattern => pattern.test(lowerMessage)) - const hasCodebaseIndicator = codebaseIndicators.some(indicator => lowerMessage.includes(indicator)) - const startsWithQuestion = questionStarters.some(starter => lowerMessage.startsWith(starter)) - const isCodebaseQuestion = matchesPattern || (hasCodebaseIndicator && startsWithQuestion) + ]; + const codebaseIndicators = ['codebase', 'code base', 'repository', 'repo', 'project structure', 'architecture', 'endpoint', 'api', 'route']; + const questionStarters = ['what is', 'what does', 'how many', 'summarize', 'explain', 'describe', 'overview']; + const matchesPattern = codebaseQuestionPatterns.some(pattern => pattern.test(lowerMessage)); + const hasCodebaseIndicator = codebaseIndicators.some(indicator => lowerMessage.includes(indicator)); + const startsWithQuestion = questionStarters.some(starter => lowerMessage.startsWith(starter)); + const isCodebaseQuestion = matchesPattern || (hasCodebaseIndicator && startsWithQuestion); const requiresComplexReasoning = isCodebaseQuestion || // Codebase questions need reasoning - reasoningKeywords.some(keyword => lowerMessage.includes(keyword)) || - complexAnalysisKeywords.some(keyword => lowerMessage.includes(keyword)) - const isLongMessage = userMessage.length > 500 + reasoningKeywords.some(keyword => lowerMessage.includes(keyword)) || + complexAnalysisKeywords.some(keyword => lowerMessage.includes(keyword)); + const isLongMessage = userMessage.length > 500; // Privacy/offline mode: removed restriction for images/PDFs // Images/PDFs now always use auto selection (remote models allowed) - const globalSettings = this._settingsService.state.globalSettings - const requiresPrivacy = false + const globalSettings = this._settingsService.state.globalSettings; + const requiresPrivacy = false; // Estimate context size needed for codebase questions // Codebase questions often need to process many files, so estimate higher context needs - let estimatedContextSize: number | undefined = undefined + let estimatedContextSize: number | undefined = undefined; if (isCodebaseQuestion) { // Codebase questions typically need: // - Base message: ~500 tokens @@ -680,35 +687,35 @@ class ChatThreadService extends Disposable implements IChatThreadService { // - Multiple file contexts: ~5000-15000 tokens (depending on codebase size) // - Response space: ~4000 tokens // Total: ~12k-22k tokens minimum, but prefer models with 128k+ for better understanding - estimatedContextSize = 20000 // Conservative estimate - prefer models with large context + estimatedContextSize = 20000; // Conservative estimate - prefer models with large context } else if (requiresComplexReasoning || isLongMessage) { // Complex reasoning tasks may need more context - estimatedContextSize = Math.max(8000, Math.ceil(userMessage.length / 2)) // Rough estimate + estimatedContextSize = Math.max(8000, Math.ceil(userMessage.length / 2)); // Rough estimate } // Detect additional task-specific flags - const isDebuggingTask = this._detectDebuggingTask(lowerMessage, hasCode) - const isCodeReviewTask = this._detectCodeReviewTask(lowerMessage) - const isTestingTask = this._detectTestingTask(lowerMessage) - const isDocumentationTask = this._detectDocumentationTask(lowerMessage) - const isPerformanceTask = this._detectPerformanceTask(lowerMessage) - const isSecurityTask = this._detectSecurityTask(lowerMessage) - const isSimpleQuestion = this._detectSimpleQuestion(userMessage, lowerMessage) - const isMathTask = this._detectMathTask(lowerMessage) - const isMultiLanguageTask = this._detectMultiLanguageTask(lowerMessage) - const isMultiStepTask = this._detectMultiStepTask(lowerMessage) + const isDebuggingTask = this._detectDebuggingTask(lowerMessage, hasCode); + const isCodeReviewTask = this._detectCodeReviewTask(lowerMessage); + const isTestingTask = this._detectTestingTask(lowerMessage); + const isDocumentationTask = this._detectDocumentationTask(lowerMessage); + const isPerformanceTask = this._detectPerformanceTask(lowerMessage); + const isSecurityTask = this._detectSecurityTask(lowerMessage); + const isSimpleQuestion = this._detectSimpleQuestion(userMessage, lowerMessage); + const isMathTask = this._detectMathTask(lowerMessage); + const isMultiLanguageTask = this._detectMultiLanguageTask(lowerMessage); + const isMultiStepTask = this._detectMultiStepTask(lowerMessage); // Build task context // Enable low-latency preference for simple questions to improve TTFS // More aggressive: enable for simple questions OR when task doesn't require complex reasoning const preferLowLatency = (isSimpleQuestion || - (!requiresComplexReasoning && - !hasImages && - !hasPDFs && - !isLongMessage && - !isMultiStepTask && - !isCodebaseQuestion && - taskType === 'chat')) // Only for general chat, not code/vision tasks + (!requiresComplexReasoning && + !hasImages && + !hasPDFs && + !isLongMessage && + !isMultiStepTask && + !isCodebaseQuestion && + taskType === 'chat')); // Only for general chat, not code/vision tasks const context: TaskContext = { taskType, @@ -732,16 +739,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { isMathTask, isMultiLanguageTask, isMultiStepTask, - } + }; try { - const routingDecision = await this._modelRouter.route(context) + const routingDecision = await this._modelRouter.route(context); // Handle abstain/clarify if (routingDecision.shouldAbstain && routingDecision.abstainReason) { - this._notificationService.info(routingDecision.abstainReason) + this._notificationService.info(routingDecision.abstainReason); // Return null to indicate we should not proceed - return null + return null; } // Log routing decision in dev mode (or always for codebase questions to help debug) @@ -769,11 +776,12 @@ class ChatThreadService extends Disposable implements IChatThreadService { // Store routing decision for later outcome tracking // We'll track the outcome when the message is actually sent - return routingDecision.modelSelection + return routingDecision.modelSelection; } catch (error) { - console.error('[Auto Model Select] Error:', error) - // Fall back to user's manual selection or null - return userManualSelection + console.error('[Auto Model Select] Error:', error); + // If error occurred, return null to trigger fallback logic + // Don't return userManualSelection (which would be 'auto') as that would bypass fallback + return null; } } @@ -808,12 +816,12 @@ class ChatThreadService extends Disposable implements IChatThreadService { * Check if a model supports vision/image inputs * Uses the same logic as modelRouter */ - private _isModelVisionCapable(modelSelection: ModelSelection, capabilities: any): boolean { + private _isModelVisionCapable(modelSelection: ModelSelection, capabilities: unknown): boolean { const name = modelSelection.modelName.toLowerCase(); const provider = modelSelection.providerName.toLowerCase(); // Known vision-capable models - if (provider === 'gemini') return true; // all Gemini models support vision + if (provider === 'gemini') { return true; } // all Gemini models support vision if (provider === 'anthropic') { return name.includes('3.5') || name.includes('3.7') || name.includes('4') || name.includes('opus') || name.includes('sonnet'); } @@ -836,16 +844,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { images?: ChatImageAttachment[], pdfs?: ChatPDFAttachment[] ): TaskType { - const lowerMessage = userMessage.toLowerCase().trim() + const lowerMessage = userMessage.toLowerCase().trim(); // PDF-specific tasks (always detect if PDFs present) if (pdfs && pdfs.length > 0) { - return 'pdf' + return 'pdf'; } // Vision tasks (always detect if images present) if (images && images.length > 0) { - return 'vision' + return 'vision'; } // Codebase/repository questions - comprehensive detection @@ -867,33 +875,33 @@ class ChatThreadService extends Disposable implements IChatThreadService { /\b(what|which|how)\s+(feature|capability|functionality|endpoint|api|route)\s+(does|has|supports?)\s+(my|this|the)\s+(codebase|repo|project|app)/i, // Questions about dependencies/tech stack /\b(what|which)\s+(technology|framework|library|dependency|package|stack)\s+(does|uses?|has)\s+(my|this|the)\s+(codebase|repo|project|app)/i, - ] + ]; const codebaseIndicators = [ 'codebase', 'code base', 'repository', 'repo', 'project structure', 'architecture', 'endpoint', 'endpoints', 'api', 'apis', 'route', 'routes', 'file structure', 'code organization', 'project layout', - ] + ]; const questionStarters = [ 'what is', 'what does', 'what are', 'what do', 'how many', 'how does', 'how do', 'summarize', 'explain', 'describe', 'overview', 'analyze', 'which', 'where', - ] + ]; // Check if it matches codebase question patterns - const matchesPattern = codebaseQuestionPatterns.some(pattern => pattern.test(lowerMessage)) - const hasCodebaseIndicator = codebaseIndicators.some(indicator => lowerMessage.includes(indicator)) - const startsWithQuestion = questionStarters.some(starter => lowerMessage.startsWith(starter)) + const matchesPattern = codebaseQuestionPatterns.some(pattern => pattern.test(lowerMessage)); + const hasCodebaseIndicator = codebaseIndicators.some(indicator => lowerMessage.includes(indicator)); + const startsWithQuestion = questionStarters.some(starter => lowerMessage.startsWith(starter)); // Codebase question if: // 1. Matches a pattern, OR // 2. Has codebase indicator AND starts with a question word - const isCodebaseQuestion = matchesPattern || (hasCodebaseIndicator && startsWithQuestion) + const isCodebaseQuestion = matchesPattern || (hasCodebaseIndicator && startsWithQuestion); if (isCodebaseQuestion) { - return 'code' // Use 'code' task type but we'll enhance scoring for codebase questions + return 'code'; // Use 'code' task type but we'll enhance scoring for codebase questions } // Implementation/action tasks - tasks that require creating or modifying code @@ -905,7 +913,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { /\b(implement|create|add|build|make|set up|configure|write|generate|develop)\s+(function|class|method|component|feature|endpoint|api|route|service|module|system|feature|functionality)\b/i, // "Implement X" or "Create X" patterns /\b(implement|create|add|build|make)\s+[a-z]+\s+(that|which|to|for)/i, - ] + ]; const implementationKeywords = [ // Action verbs @@ -917,29 +925,29 @@ class ChatThreadService extends Disposable implements IChatThreadService { 'create new', 'implement new', 'add new', 'build new', 'set up', 'set up a', 'configure', 'configure a', 'develop', 'develop a', 'build out', - ] + ]; - const hasImplementationPattern = implementationPatterns.some(pattern => pattern.test(lowerMessage)) - const hasImplementationKeyword = implementationKeywords.some(keyword => lowerMessage.includes(keyword)) + const hasImplementationPattern = implementationPatterns.some(pattern => pattern.test(lowerMessage)); + const hasImplementationKeyword = implementationKeywords.some(keyword => lowerMessage.includes(keyword)); // Code tasks - check for actual code patterns or explicit code requests - const hasCodeBlock = /```[\s\S]+?```/.test(userMessage) || /`[^`\n]{10,}`/.test(userMessage) + const hasCodeBlock = /```[\s\S]+?```/.test(userMessage) || /`[^`\n]{10,}`/.test(userMessage); // Implementation task if it matches patterns/keywords OR has code blocks if (hasCodeBlock || hasImplementationPattern || hasImplementationKeyword) { - return 'code' + return 'code'; } // Web search tasks - only if very explicit - const explicitWebSearchKeywords = ['search the web', 'search online', 'look up online', 'google', 'duckduckgo', 'web search', 'search internet'] + const explicitWebSearchKeywords = ['search the web', 'search online', 'look up online', 'google', 'duckduckgo', 'web search', 'search internet']; if (explicitWebSearchKeywords.some(keyword => lowerMessage.includes(keyword))) { - return 'web_search' + return 'web_search'; } // Default to general chat (prefers quality models) // Complexity detection (reasoning, long messages) is handled in _autoSelectModel // and passed to the router via TaskContext - return 'chat' + return 'chat'; } /** @@ -955,9 +963,9 @@ class ChatThreadService extends Disposable implements IChatThreadService { /import\s+.*from/, // Import statements /const\s+\w+\s*=/, // Const declarations /let\s+\w+\s*=/, // Let declarations - ] + ]; - return codePatterns.some(pattern => pattern.test(message)) + return codePatterns.some(pattern => pattern.test(message)); } /** @@ -968,18 +976,18 @@ class ChatThreadService extends Disposable implements IChatThreadService { 'fix error', 'debug', 'why is this failing', 'error message', 'exception', 'stack trace', 'why doesn\'t this work', 'not working', 'broken', 'crash', 'bug', 'fix bug', 'troubleshoot', 'issue', 'problem', 'failing', 'failed', 'error', 'errors' - ] + ]; const errorPatterns = [ /error\s+(message|occurred|happened|in|at)/i, /exception\s+(thrown|occurred|in|at)/i, /stack\s+trace/i, /why\s+(is|does|isn\'t|doesn\'t).*work/i, /why\s+(is|does).*fail/i, - ] + ]; return debuggingKeywords.some(keyword => lowerMessage.includes(keyword)) || - errorPatterns.some(pattern => pattern.test(lowerMessage)) || - (hasCode && (lowerMessage.includes('error') || lowerMessage.includes('exception'))) + errorPatterns.some(pattern => pattern.test(lowerMessage)) || + (hasCode && (lowerMessage.includes('error') || lowerMessage.includes('exception'))); } /** @@ -990,16 +998,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { 'review', 'refactor', 'improve code', 'code quality', 'best practices', 'clean up', 'is this good code', 'how can i improve', 'refactor this', 'code review', 'optimize', 'make it better', 'improve this', 'suggest improvements' - ] + ]; const reviewPatterns = [ /review\s+(this|my|the)\s+(code|function|class|method)/i, /refactor\s+(this|my|the)/i, /how\s+(can|to)\s+(improve|refactor|optimize)/i, /is\s+(this|my|the)\s+(code|implementation)\s+(good|correct|proper)/i, - ] + ]; return reviewKeywords.some(keyword => lowerMessage.includes(keyword)) || - reviewPatterns.some(pattern => pattern.test(lowerMessage)) + reviewPatterns.some(pattern => pattern.test(lowerMessage)); } /** @@ -1010,16 +1018,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { 'write test', 'add test', 'test coverage', 'unit test', 'integration test', 'test for', 'how to test', 'create test', 'testing', 'test case', 'test suite', 'write tests', 'add tests', 'test this', 'test the' - ] + ]; const testingPatterns = [ /write\s+(a|an|the|unit|integration)\s+test/i, /add\s+(a|an|unit|integration)\s+test/i, /create\s+(a|an|unit|integration)\s+test/i, /test\s+(for|this|the|coverage)/i, - ] + ]; return testingKeywords.some(keyword => lowerMessage.includes(keyword)) || - testingPatterns.some(pattern => pattern.test(lowerMessage)) + testingPatterns.some(pattern => pattern.test(lowerMessage)); } /** @@ -1030,16 +1038,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { 'write doc', 'documentation', 'comment', 'explain code', 'readme', 'api doc', 'document this', 'add comments', 'write readme', 'document', 'docs', 'comment', 'comments', 'javadoc', 'jsdoc', 'docstring' - ] + ]; const docPatterns = [ /write\s+(documentation|doc|readme|comments)/i, /add\s+(documentation|doc|comments|comment)/i, /document\s+(this|my|the)/i, /explain\s+(this|my|the)\s+(code|function|class)/i, - ] + ]; return docKeywords.some(keyword => lowerMessage.includes(keyword)) || - docPatterns.some(pattern => pattern.test(lowerMessage)) + docPatterns.some(pattern => pattern.test(lowerMessage)); } /** @@ -1050,16 +1058,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { 'optimize', 'performance', 'speed up', 'make faster', 'bottleneck', 'profiling', 'how to optimize', 'performance issue', 'slow', 'faster', 'speed', 'efficiency', 'optimization', 'improve performance', 'performance problem' - ] + ]; const perfPatterns = [ /optimize\s+(this|my|the|for)/i, /performance\s+(issue|problem|optimization|improvement)/i, /how\s+to\s+(optimize|improve\s+performance|speed\s+up)/i, /make\s+(this|it|the)\s+faster/i, - ] + ]; return perfKeywords.some(keyword => lowerMessage.includes(keyword)) || - perfPatterns.some(pattern => pattern.test(lowerMessage)) + perfPatterns.some(pattern => pattern.test(lowerMessage)); } /** @@ -1070,16 +1078,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { 'security', 'vulnerability', 'secure', 'authentication', 'authorization', 'encryption', 'is this secure', 'security issue', 'vulnerable', 'vulnerabilities', 'secure this', 'security best practices', 'security review', 'security audit', 'xss', 'csrf', 'sql injection' - ] + ]; const securityPatterns = [ /security\s+(issue|problem|vulnerability|review|audit)/i, /is\s+(this|my|the)\s+secure/i, /how\s+to\s+secure/i, /make\s+(this|it|the)\s+secure/i, - ] + ]; return securityKeywords.some(keyword => lowerMessage.includes(keyword)) || - securityPatterns.some(pattern => pattern.test(lowerMessage)) + securityPatterns.some(pattern => pattern.test(lowerMessage)); } /** @@ -1089,16 +1097,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { private _detectSimpleQuestion(message: string, lowerMessage: string): boolean { // Exclude complex tasks first if (lowerMessage.includes('codebase') || - lowerMessage.includes('repository') || - lowerMessage.includes('architecture') || - lowerMessage.includes('analyze') || - lowerMessage.includes('refactor') || - lowerMessage.includes('implement') || - lowerMessage.includes('debug') || - lowerMessage.includes('error') || - lowerMessage.includes('fix') || - lowerMessage.includes('review')) { - return false + lowerMessage.includes('repository') || + lowerMessage.includes('architecture') || + lowerMessage.includes('analyze') || + lowerMessage.includes('refactor') || + lowerMessage.includes('implement') || + lowerMessage.includes('debug') || + lowerMessage.includes('error') || + lowerMessage.includes('fix') || + lowerMessage.includes('review')) { + return false; } // Simple questions are typically: @@ -1112,8 +1120,8 @@ class ChatThreadService extends Disposable implements IChatThreadService { 'explain', 'tell me', 'describe', 'when', 'where', 'why', 'who', 'can you', 'could you', 'would you' - ] - const isQuestion = simpleQuestionStarters.some(starter => lowerMessage.startsWith(starter)) + ]; + const isQuestion = simpleQuestionStarters.some(starter => lowerMessage.startsWith(starter)); // Also check for simple question patterns const simplePatterns = [ @@ -1122,13 +1130,13 @@ class ChatThreadService extends Disposable implements IChatThreadService { /^explain\s+/, /^tell\s+me\s+/, /^describe\s+/ - ] - const matchesPattern = simplePatterns.some(pattern => pattern.test(lowerMessage)) + ]; + const matchesPattern = simplePatterns.some(pattern => pattern.test(lowerMessage)); - return (isQuestion || matchesPattern) && message.length < 200 + return (isQuestion || matchesPattern) && message.length < 200; } - return false + return false; } /** @@ -1138,16 +1146,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { const mathKeywords = [ 'calculate', 'math', 'algorithm', 'formula', 'compute', 'statistics', 'calculation', 'mathematical', 'equation', 'solve', 'numerical', 'arithmetic' - ] + ]; const mathPatterns = [ /calculate\s+(this|the|a|an)/i, /solve\s+(this|the|a|an|for)/i, /math\s+(problem|question|calculation)/i, /formula\s+(for|to|of)/i, - ] + ]; return mathKeywords.some(keyword => lowerMessage.includes(keyword)) || - mathPatterns.some(pattern => pattern.test(lowerMessage)) + mathPatterns.some(pattern => pattern.test(lowerMessage)); } /** @@ -1157,16 +1165,16 @@ class ChatThreadService extends Disposable implements IChatThreadService { const multiLangKeywords = [ 'translate code', 'convert to', 'port to', 'rewrite in', 'convert from', 'multiple languages', 'different language', 'language conversion' - ] + ]; const multiLangPatterns = [ /translate\s+(code|this|from|to)/i, /convert\s+(code|this|from|to)/i, /port\s+(to|from)/i, /rewrite\s+in/i, - ] + ]; return multiLangKeywords.some(keyword => lowerMessage.includes(keyword)) || - multiLangPatterns.some(pattern => pattern.test(lowerMessage)) + multiLangPatterns.some(pattern => pattern.test(lowerMessage)); } /** @@ -1174,70 +1182,70 @@ class ChatThreadService extends Disposable implements IChatThreadService { */ private _detectMultiStepTask(lowerMessage: string): boolean { // Multiple action verbs or "and" in requests indicate multi-step tasks - const actionVerbs = ['implement', 'create', 'add', 'build', 'make', 'set up', 'configure', 'write', 'generate', 'develop', 'fix', 'update', 'modify'] - const verbCount = actionVerbs.filter(verb => lowerMessage.includes(verb)).length + const actionVerbs = ['implement', 'create', 'add', 'build', 'make', 'set up', 'configure', 'write', 'generate', 'develop', 'fix', 'update', 'modify']; + const verbCount = actionVerbs.filter(verb => lowerMessage.includes(verb)).length; // Multiple "and" conjunctions suggest multiple steps - const andCount = (lowerMessage.match(/\sand\s/g) || []).length + const andCount = (lowerMessage.match(/\sand\s/g) || []).length; // Multi-step indicators - const multiStepKeywords = ['then', 'after that', 'next', 'also', 'additionally', 'furthermore', 'step', 'steps'] - const hasMultiStepKeywords = multiStepKeywords.some(keyword => lowerMessage.includes(keyword)) + const multiStepKeywords = ['then', 'after that', 'next', 'also', 'additionally', 'furthermore', 'step', 'steps']; + const hasMultiStepKeywords = multiStepKeywords.some(keyword => lowerMessage.includes(keyword)); - return verbCount >= 2 || andCount >= 2 || hasMultiStepKeywords + return verbCount >= 2 || andCount >= 2 || hasMultiStepKeywords; } private _swapOutLatestStreamingToolWithResult = (threadId: string, tool: ChatMessage & { role: 'tool' }) => { - const messages = this.state.allThreads[threadId]?.messages - if (!messages) return false - const lastMsg = messages[messages.length - 1] - if (!lastMsg) return false + const messages = this.state.allThreads[threadId]?.messages; + if (!messages) { return false; } + const lastMsg = messages[messages.length - 1]; + if (!lastMsg) { return false; } if (lastMsg.role === 'tool' && lastMsg.type !== 'invalid_params') { - this._editMessageInThread(threadId, messages.length - 1, tool) - return true + this._editMessageInThread(threadId, messages.length - 1, tool); + return true; } - return false - } + return false; + }; private _updateLatestTool = (threadId: string, tool: ChatMessage & { role: 'tool' }) => { - const swapped = this._swapOutLatestStreamingToolWithResult(threadId, tool) - if (swapped) return - this._addMessageToThread(threadId, tool) - } + const swapped = this._swapOutLatestStreamingToolWithResult(threadId, tool); + if (swapped) { return; } + this._addMessageToThread(threadId, tool); + }; approveLatestToolRequest(threadId: string) { - const thread = this.state.allThreads[threadId] - if (!thread) return // should never happen + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } // should never happen - const lastMsg = thread.messages[thread.messages.length - 1] - if (!(lastMsg.role === 'tool' && lastMsg.type === 'tool_request')) return // should never happen + const lastMsg = thread.messages[thread.messages.length - 1]; + if (!(lastMsg.role === 'tool' && lastMsg.type === 'tool_request')) { return; } // should never happen - const callThisToolFirst: ToolMessage = lastMsg + const callThisToolFirst: ToolMessage = lastMsg; this._wrapRunAgentToNotify( this._runChatAgent({ callThisToolFirst, threadId, ...this._currentModelSelectionProps() }) , threadId - ) + ); } rejectLatestToolRequest(threadId: string) { - const thread = this.state.allThreads[threadId] - if (!thread) return // should never happen + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } // should never happen - const lastMsg = thread.messages[thread.messages.length - 1] + const lastMsg = thread.messages[thread.messages.length - 1]; - let params: ToolCallParams + let params: ToolCallParams; if (lastMsg.role === 'tool' && lastMsg.type !== 'invalid_params') { - params = lastMsg.params + params = lastMsg.params; } - else return + else { return; } - const { name, id, rawParams, mcpServerName } = lastMsg + const { name, id, rawParams, mcpServerName } = lastMsg; - const errorMessage = this.toolErrMsgs.rejected - this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', params: params, name: name, content: errorMessage, result: null, id, rawParams, mcpServerName }) - this._setStreamState(threadId, undefined) + const errorMessage = this.toolErrMsgs.rejected; + this._updateLatestTool(threadId, { role: 'tool', type: 'rejected', params: params, name: name, content: errorMessage, result: null, id, rawParams, mcpServerName }); + this._setStreamState(threadId, undefined); } // Plan management methods @@ -1245,13 +1253,13 @@ class ChatThreadService extends Disposable implements IChatThreadService { // To test the UI, you can create a plan manually like: // chatThreadService.addTestPlan({ threadId: 'xxx', summary: 'Test plan', steps: [...] }) - approvePlan(opts: { threadId: string, messageIdx: number }) { - const thread = this.state.allThreads[opts.threadId] - if (!thread) return - const message = thread.messages[opts.messageIdx] - if (!message || message.role !== 'plan') return + approvePlan(opts: { threadId: string; messageIdx: number }) { + const thread = this.state.allThreads[opts.threadId]; + if (!thread) { return; } + const message = thread.messages[opts.messageIdx]; + if (!message || message.role !== 'plan') { return; } - const plan = message as PlanMessage + const plan = message as PlanMessage; const updatedPlan: PlanMessage = { ...plan, approvalState: 'approved', @@ -1261,47 +1269,47 @@ class ChatThreadService extends Disposable implements IChatThreadService { ...step, status: step.disabled ? 'skipped' as StepStatus : (step.status || 'queued' as StepStatus) })) - } - this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan) + }; + this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan); // CRITICAL: Invalidate plan cache so checkPlanGenerated() sees the updated approvalState - this._planCache.delete(opts.threadId) + this._planCache.delete(opts.threadId); // Trigger plan execution this._wrapRunAgentToNotify( this._runChatAgent({ threadId: opts.threadId, ...this._currentModelSelectionProps() }), opts.threadId, - ) + ); } - rejectPlan(opts: { threadId: string, messageIdx: number }) { - const thread = this.state.allThreads[opts.threadId] - if (!thread) return - const message = thread.messages[opts.messageIdx] - if (!message || message.role !== 'plan') return + rejectPlan(opts: { threadId: string; messageIdx: number }) { + const thread = this.state.allThreads[opts.threadId]; + if (!thread) { return; } + const message = thread.messages[opts.messageIdx]; + if (!message || message.role !== 'plan') { return; } - const plan = message as PlanMessage + const plan = message as PlanMessage; const updatedPlan: PlanMessage = { ...plan, approvalState: 'aborted' - } - this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan) + }; + this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan); } - editPlan(opts: { threadId: string, messageIdx: number, updatedPlan: PlanMessage }) { - const thread = this.state.allThreads[opts.threadId] - if (!thread) return - const message = thread.messages[opts.messageIdx] - if (!message || message.role !== 'plan') return + editPlan(opts: { threadId: string; messageIdx: number; updatedPlan: PlanMessage }) { + const thread = this.state.allThreads[opts.threadId]; + if (!thread) { return; } + const message = thread.messages[opts.messageIdx]; + if (!message || message.role !== 'plan') { return; } - this._editMessageInThread(opts.threadId, opts.messageIdx, opts.updatedPlan) + this._editMessageInThread(opts.threadId, opts.messageIdx, opts.updatedPlan); } - toggleStepDisabled(opts: { threadId: string, messageIdx: number, stepNumber: number }) { - const thread = this.state.allThreads[opts.threadId] - if (!thread) return - const message = thread.messages[opts.messageIdx] - if (!message || message.role !== 'plan') return + toggleStepDisabled(opts: { threadId: string; messageIdx: number; stepNumber: number }) { + const thread = this.state.allThreads[opts.threadId]; + if (!thread) { return; } + const message = thread.messages[opts.messageIdx]; + if (!message || message.role !== 'plan') { return; } - const plan = message as PlanMessage + const plan = message as PlanMessage; const updatedPlan: PlanMessage = { ...plan, steps: plan.steps.map(step => @@ -1309,279 +1317,279 @@ class ChatThreadService extends Disposable implements IChatThreadService { ? { ...step, disabled: !step.disabled } : step ) - } - this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan) + }; + this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan); } - reorderPlanSteps(opts: { threadId: string, messageIdx: number, newStepOrder: number[] }) { - const thread = this.state.allThreads[opts.threadId] - if (!thread) return - const message = thread.messages[opts.messageIdx] - if (!message || message.role !== 'plan') return + reorderPlanSteps(opts: { threadId: string; messageIdx: number; newStepOrder: number[] }) { + const thread = this.state.allThreads[opts.threadId]; + if (!thread) { return; } + const message = thread.messages[opts.messageIdx]; + if (!message || message.role !== 'plan') { return; } - const plan = message as PlanMessage - const stepMap = new Map(plan.steps.map(s => [s.stepNumber, s])) + const plan = message as PlanMessage; + const stepMap = new Map(plan.steps.map(s => [s.stepNumber, s])); const reorderedSteps = opts.newStepOrder .map(stepNum => stepMap.get(stepNum)) .filter((s): s is PlanStep => s !== undefined) - .map((step, idx) => ({ ...step, stepNumber: idx + 1 })) + .map((step, idx) => ({ ...step, stepNumber: idx + 1 })); const updatedPlan: PlanMessage = { ...plan, steps: reorderedSteps - } - this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan) + }; + this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan); } async pauseAgentExecution(opts: { threadId: string }): Promise { // TODO: Implement pause logic - freeze current step, save state - await this.abortRunning(opts.threadId) - const thread = this.state.allThreads[opts.threadId] - if (!thread) return + await this.abortRunning(opts.threadId); + const thread = this.state.allThreads[opts.threadId]; + if (!thread) { return; } // Find current plan and update current step to paused - const planIdx = findLastIdx(thread.messages, (m: ChatMessage) => m.role === 'plan') ?? -1 + const planIdx = findLastIdx(thread.messages, (m: ChatMessage) => m.role === 'plan') ?? -1; if (planIdx >= 0) { - const plan = thread.messages[planIdx] as PlanMessage - const runningStepIdx = plan.steps.findIndex(s => s.status === 'running') + const plan = thread.messages[planIdx] as PlanMessage; + const runningStepIdx = plan.steps.findIndex(s => s.status === 'running'); if (runningStepIdx >= 0) { - const updatedSteps = [...plan.steps] - updatedSteps[runningStepIdx] = { ...updatedSteps[runningStepIdx], status: 'paused' } - const updatedPlan: PlanMessage = { ...plan, steps: updatedSteps } - this._editMessageInThread(opts.threadId, planIdx, updatedPlan) + const updatedSteps = [...plan.steps]; + updatedSteps[runningStepIdx] = { ...updatedSteps[runningStepIdx], status: 'paused' }; + const updatedPlan: PlanMessage = { ...plan, steps: updatedSteps }; + this._editMessageInThread(opts.threadId, planIdx, updatedPlan); } } } async resumeAgentExecution(opts: { threadId: string }): Promise { - const thread = this.state.allThreads[opts.threadId] - if (!thread) return + const thread = this.state.allThreads[opts.threadId]; + if (!thread) { return; } - const planIdx = findLastIdx(thread.messages, (m: ChatMessage) => m.role === 'plan') ?? -1 + const planIdx = findLastIdx(thread.messages, (m: ChatMessage) => m.role === 'plan') ?? -1; if (planIdx >= 0) { - const plan = thread.messages[planIdx] as PlanMessage - const pausedStepIdx = plan.steps.findIndex(s => s.status === 'paused') + const plan = thread.messages[planIdx] as PlanMessage; + const pausedStepIdx = plan.steps.findIndex(s => s.status === 'paused'); if (pausedStepIdx >= 0) { - const updatedSteps = [...plan.steps] - updatedSteps[pausedStepIdx] = { ...updatedSteps[pausedStepIdx], status: 'queued' } + const updatedSteps = [...plan.steps]; + updatedSteps[pausedStepIdx] = { ...updatedSteps[pausedStepIdx], status: 'queued' }; const updatedPlan: PlanMessage = { ...plan, steps: updatedSteps, approvalState: 'executing' - } - this._editMessageInThread(opts.threadId, planIdx, updatedPlan) + }; + this._editMessageInThread(opts.threadId, planIdx, updatedPlan); // Resume execution from this step this._wrapRunAgentToNotify( this._runChatAgent({ threadId: opts.threadId, ...this._currentModelSelectionProps() }), opts.threadId, - ) + ); } } } - async retryStep(opts: { threadId: string, messageIdx: number, stepNumber: number }): Promise { - const thread = this.state.allThreads[opts.threadId] - if (!thread) return - const message = thread.messages[opts.messageIdx] - if (!message || message.role !== 'plan') return + async retryStep(opts: { threadId: string; messageIdx: number; stepNumber: number }): Promise { + const thread = this.state.allThreads[opts.threadId]; + if (!thread) { return; } + const message = thread.messages[opts.messageIdx]; + if (!message || message.role !== 'plan') { return; } - const plan = message as PlanMessage + const plan = message as PlanMessage; const updatedSteps = plan.steps.map(step => step.stepNumber === opts.stepNumber ? { ...step, status: 'queued' as StepStatus, error: undefined, startTime: undefined, endTime: undefined } : step - ) + ); const updatedPlan: PlanMessage = { ...plan, steps: updatedSteps, approvalState: plan.approvalState === 'completed' ? 'executing' : plan.approvalState - } - this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan) + }; + this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan); // Trigger step execution this._wrapRunAgentToNotify( this._runChatAgent({ threadId: opts.threadId, ...this._currentModelSelectionProps() }), opts.threadId, - ) + ); } - skipStep(opts: { threadId: string, messageIdx: number, stepNumber: number }) { - const thread = this.state.allThreads[opts.threadId] - if (!thread) return - const message = thread.messages[opts.messageIdx] - if (!message || message.role !== 'plan') return + skipStep(opts: { threadId: string; messageIdx: number; stepNumber: number }) { + const thread = this.state.allThreads[opts.threadId]; + if (!thread) { return; } + const message = thread.messages[opts.messageIdx]; + if (!message || message.role !== 'plan') { return; } - const plan = message as PlanMessage + const plan = message as PlanMessage; const updatedSteps = plan.steps.map(step => step.stepNumber === opts.stepNumber ? { ...step, status: 'skipped' as StepStatus } : step - ) - const updatedPlan: PlanMessage = { ...plan, steps: updatedSteps } - this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan) + ); + const updatedPlan: PlanMessage = { ...plan, steps: updatedSteps }; + this._editMessageInThread(opts.threadId, opts.messageIdx, updatedPlan); // After skipping, resume execution to continue with the next queued step this._wrapRunAgentToNotify( this._runChatAgent({ threadId: opts.threadId, ...this._currentModelSelectionProps() }), opts.threadId, - ) + ); } - async rollbackToStep(opts: { threadId: string, messageIdx: number, stepNumber: number }): Promise { - const thread = this.state.allThreads[opts.threadId] - if (!thread) return - const message = thread.messages[opts.messageIdx] - if (!message || message.role !== 'plan') return + async rollbackToStep(opts: { threadId: string; messageIdx: number; stepNumber: number }): Promise { + const thread = this.state.allThreads[opts.threadId]; + if (!thread) { return; } + const message = thread.messages[opts.messageIdx]; + if (!message || message.role !== 'plan') { return; } - const plan = message as PlanMessage - const step = plan.steps.find(s => s.stepNumber === opts.stepNumber) - if (!step || step.checkpointIdx === undefined || step.checkpointIdx === null) return + const plan = message as PlanMessage; + const step = plan.steps.find(s => s.stepNumber === opts.stepNumber); + if (!step || step.checkpointIdx === undefined || step.checkpointIdx === null) { return; } // Rollback to checkpoint before this step this.jumpToCheckpointBeforeMessageIdx({ threadId: opts.threadId, messageIdx: step.checkpointIdx, jumpToUserModified: false - }) + }); } // Plan execution tracking helpers - cached for performance - private _planCache: Map = new Map() - private readonly PLAN_CACHE_TTL = 100 // ms - invalidate cache after message changes + private _planCache: Map = new Map(); + private readonly PLAN_CACHE_TTL = 100; // ms - invalidate cache after message changes - private _getCurrentPlan(threadId: string, forceRefresh = false): { plan: PlanMessage, planIdx: number } | undefined { - const thread = this.state.allThreads[threadId] - if (!thread) return undefined + private _getCurrentPlan(threadId: string, forceRefresh = false): { plan: PlanMessage; planIdx: number } | undefined { + const thread = this.state.allThreads[threadId]; + if (!thread) { return undefined; } // Fast path: check cache first (only if messages haven't changed significantly) if (!forceRefresh) { - const cached = this._planCache.get(threadId) + const cached = this._planCache.get(threadId); if (cached && cached.lastChecked > Date.now() - this.PLAN_CACHE_TTL && cached.planIdx < thread.messages.length) { // Verify cached plan is still valid - const cachedPlan = thread.messages[cached.planIdx] + const cachedPlan = thread.messages[cached.planIdx]; if (cachedPlan && cachedPlan.role === 'plan') { - const plan = cachedPlan as PlanMessage + const plan = cachedPlan as PlanMessage; // Return plan regardless of approvalState (pending, approved, executing all need to be seen) - return { plan, planIdx: cached.planIdx } + return { plan, planIdx: cached.planIdx }; } } } // Slow path: find plan (only when cache misses or forced) - const planIdx = findLastIdx(thread.messages, (m: ChatMessage) => m.role === 'plan') ?? -1 + const planIdx = findLastIdx(thread.messages, (m: ChatMessage) => m.role === 'plan') ?? -1; if (planIdx < 0) { - this._planCache.set(threadId, null) - return undefined + this._planCache.set(threadId, null); + return undefined; } - const plan = thread.messages[planIdx] as PlanMessage + const plan = thread.messages[planIdx] as PlanMessage; // Cache result (for all approval states) - const result = { plan, planIdx, lastChecked: Date.now() } - this._planCache.set(threadId, result) - return { plan, planIdx } + const result = { plan, planIdx, lastChecked: Date.now() }; + this._planCache.set(threadId, result); + return { plan, planIdx }; } - private _getCurrentStep(threadId: string, forceRefresh = false): { plan: PlanMessage, planIdx: number, step: PlanStep, stepIdx: number } | undefined { - const planInfo = this._getCurrentPlan(threadId, forceRefresh) - if (!planInfo) return undefined - const { plan, planIdx } = planInfo + private _getCurrentStep(threadId: string, forceRefresh = false): { plan: PlanMessage; planIdx: number; step: PlanStep; stepIdx: number } | undefined { + const planInfo = this._getCurrentPlan(threadId, forceRefresh); + if (!planInfo) { return undefined; } + const { plan, planIdx } = planInfo; // Find first step that's queued or running const stepIdx = plan.steps.findIndex(s => !s.disabled && (s.status === 'queued' || s.status === 'running' || s.status === 'paused') - ) - if (stepIdx < 0) return undefined + ); + if (stepIdx < 0) { return undefined; } - return { plan, planIdx, step: plan.steps[stepIdx], stepIdx } + return { plan, planIdx, step: plan.steps[stepIdx], stepIdx }; } private _updatePlanStep(threadId: string, planIdx: number, stepIdx: number, updates: Partial) { - const thread = this.state.allThreads[threadId] - if (!thread) return - const message = thread.messages[planIdx] - if (!message || message.role !== 'plan') return - - const plan = message as PlanMessage - const updatedSteps = [...plan.steps] - updatedSteps[stepIdx] = { ...updatedSteps[stepIdx], ...updates } - const updatedPlan: PlanMessage = { ...plan, steps: updatedSteps } - this._editMessageInThread(threadId, planIdx, updatedPlan) + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } + const message = thread.messages[planIdx]; + if (!message || message.role !== 'plan') { return; } + + const plan = message as PlanMessage; + const updatedSteps = [...plan.steps]; + updatedSteps[stepIdx] = { ...updatedSteps[stepIdx], ...updates }; + const updatedPlan: PlanMessage = { ...plan, steps: updatedSteps }; + this._editMessageInThread(threadId, planIdx, updatedPlan); // Invalidate cache after update - this._planCache.delete(threadId) + this._planCache.delete(threadId); } // Fast internal versions that take step directly (avoid lookup) - private _linkToolCallToStepInternal(threadId: string, toolId: string, currentStep: { plan: PlanMessage, planIdx: number, step: PlanStep, stepIdx: number }, stepNumber?: number) { - const { planIdx, step, stepIdx } = currentStep + private _linkToolCallToStepInternal(threadId: string, toolId: string, currentStep: { plan: PlanMessage; planIdx: number; step: PlanStep; stepIdx: number }, stepNumber?: number) { + const { planIdx, step, stepIdx } = currentStep; // If stepNumber provided, verify it matches - if (stepNumber !== undefined && step.stepNumber !== stepNumber) return + if (stepNumber !== undefined && step.stepNumber !== stepNumber) { return; } - const toolCalls = step.toolCalls || [] + const toolCalls = step.toolCalls || []; if (!toolCalls.includes(toolId)) { this._updatePlanStep(threadId, planIdx, stepIdx, { toolCalls: [...toolCalls, toolId] - }) + }); } } - private _markStepCompletedInternal(threadId: string, currentStep: { plan: PlanMessage, planIdx: number, step: PlanStep, stepIdx: number }, succeeded: boolean, error?: string) { - const { planIdx, stepIdx } = currentStep + private _markStepCompletedInternal(threadId: string, currentStep: { plan: PlanMessage; planIdx: number; step: PlanStep; stepIdx: number }, succeeded: boolean, error?: string) { + const { planIdx, stepIdx } = currentStep; const updates: Partial = { status: succeeded ? 'succeeded' : 'failed', endTime: Date.now(), error: error - } - this._updatePlanStep(threadId, planIdx, stepIdx, updates) + }; + this._updatePlanStep(threadId, planIdx, stepIdx, updates); } - private _startNextStep(threadId: string): { step: PlanStep, checkpointIdx: number } | undefined { + private _startNextStep(threadId: string): { step: PlanStep; checkpointIdx: number } | undefined { // Force refresh to get latest plan state (may have been updated) - const planInfo = this._getCurrentPlan(threadId, true) - if (!planInfo) return undefined - const { plan, planIdx } = planInfo + const planInfo = this._getCurrentPlan(threadId, true); + if (!planInfo) { return undefined; } + const { plan, planIdx } = planInfo; // Find next queued step (not disabled, queued status) const stepIdx = plan.steps.findIndex(s => !s.disabled && s.status === 'queued' - ) - if (stepIdx < 0) return undefined + ); + if (stepIdx < 0) { return undefined; } - const step = plan.steps[stepIdx] + const step = plan.steps[stepIdx]; // Create checkpoint before starting step - this._addUserCheckpoint({ threadId }) - const thread = this.state.allThreads[threadId] - if (!thread) return undefined - const checkpointIdx = thread.messages.length - 1 + this._addUserCheckpoint({ threadId }); + const thread = this.state.allThreads[threadId]; + if (!thread) { return undefined; } + const checkpointIdx = thread.messages.length - 1; // Update step to running and link checkpoint this._updatePlanStep(threadId, planIdx, stepIdx, { status: 'running', startTime: Date.now(), checkpointIdx: checkpointIdx - }) + }); - return { step, checkpointIdx } + return { step, checkpointIdx }; } private _computeMCPServerOfToolName = (toolName: string) => { - return this._mcpService.getMCPTools()?.find(t => t.name === toolName)?.mcpServerName - } + return this._mcpService.getMCPTools()?.find(t => t.name === toolName)?.mcpServerName; + }; // Check if user request warrants plan generation private _shouldGeneratePlan(threadId: string): boolean { // Honor one-shot suppression flag (used by simple Quick Actions) if (this._suppressPlanOnceByThread[threadId]) { - delete this._suppressPlanOnceByThread[threadId] - return false + delete this._suppressPlanOnceByThread[threadId]; + return false; } - const thread = this.state.allThreads[threadId] - if (!thread) return false + const thread = this.state.allThreads[threadId]; + if (!thread) { return false; } - const lastUserMessage = thread.messages.filter(m => m.role === 'user').pop() - if (!lastUserMessage || lastUserMessage.role !== 'user') return false + const lastUserMessage = thread.messages.filter(m => m.role === 'user').pop(); + if (!lastUserMessage || lastUserMessage.role !== 'user') { return false; } - const userRequest = (lastUserMessage.displayContent || '').toLowerCase() + const userRequest = (lastUserMessage.displayContent || '').toLowerCase(); // Detect complex multi-step tasks that should have plans const complexTaskIndicators = [ @@ -1596,18 +1604,18 @@ class ChatThreadService extends Disposable implements IChatThreadService { 'create.*with', 'add.*with.*and', // Structured requests 'authentication.*system', 'api.*with.*tests', 'full.*stack' - ] + ]; const hasComplexIndicator = complexTaskIndicators.some(pattern => { - const regex = new RegExp(pattern, 'i') - return regex.test(userRequest) - }) + const regex = new RegExp(pattern, 'i'); + return regex.test(userRequest); + }); // Also check for multiple action verbs (suggests multiple steps) - const actionVerbs = ['create', 'add', 'edit', 'delete', 'update', 'refactor', 'implement', 'build', 'set up', 'configure', 'test'] - const actionCount = actionVerbs.filter(verb => userRequest.includes(verb)).length + const actionVerbs = ['create', 'add', 'edit', 'delete', 'update', 'refactor', 'implement', 'build', 'set up', 'configure', 'test']; + const actionCount = actionVerbs.filter(verb => userRequest.includes(verb)).length; - return hasComplexIndicator || actionCount >= 3 + return hasComplexIndicator || actionCount >= 3; } // Generate plan from user request by asking LLM @@ -1616,61 +1624,61 @@ class ChatThreadService extends Disposable implements IChatThreadService { modelSelection: ModelSelection | null, modelSelectionOptions: ModelSelectionOptions | undefined ): Promise { - const thread = this.state.allThreads[threadId] - if (!thread) return + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } - const lastUserMessage = thread.messages.filter(m => m.role === 'user').pop() - if (!lastUserMessage || lastUserMessage.role !== 'user') return + const lastUserMessage = thread.messages.filter(m => m.role === 'user').pop(); + if (!lastUserMessage || lastUserMessage.role !== 'user') { return; } - const userRequest = lastUserMessage.displayContent || '' + const userRequest = lastUserMessage.displayContent || ''; // Prepare messages for plan generation const planPrompt = `The user has requested: "${userRequest}" Please generate a structured execution plan for this task. Output your plan in the following JSON format: -{ - "summary": "Brief overall plan summary", - "steps": [ - { - "stepNumber": 1, - "description": "Step description", - "tools": ["tool_name1", "tool_name2"], - "files": ["path/to/file1.ts", "path/to/file2.ts"] - }, - { - "stepNumber": 2, - "description": "Next step description", - "tools": ["tool_name"], - "files": ["path/to/file.ts"] - } - ] -} + { + "summary": "Brief overall plan summary", + "steps": [ + { + "stepNumber": 1, + "description": "Step description", + "tools": ["tool_name1", "tool_name2"], + "files": ["path/to/file1.ts", "path/to/file2.ts"] + }, + { + "stepNumber": 2, + "description": "Next step description", + "tools": ["tool_name"], + "files": ["path/to/file.ts"] + } + ] + } Think through the task carefully. Break it down into logical steps. For each step: - Describe what needs to be done - List the tools that will be needed (e.g., read_file, edit_file, create_file_or_folder, run_command, search_for_files) - List files that will be affected (if known or likely) -Output ONLY the JSON, no other text. Start with { and end with }.` +Output ONLY the JSON, no other text. Start with { and end with }.`; // Send plan generation request - const chatMessages = thread.messages.slice(0, -1) // All messages except last user message + const chatMessages = thread.messages.slice(0, -1); // All messages except last user message const planRequest: ChatMessage = { role: 'user', content: planPrompt, displayContent: planPrompt, selections: null, state: { stagingSelections: [], isBeingEdited: false } - } + }; const { messages } = await this._convertToLLMMessagesService.prepareLLMChatMessages({ chatMessages: [...chatMessages, planRequest], modelSelection, chatMode: 'normal' // Use 'normal' mode to prevent tool execution during plan generation - }) + }); - this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: 'Generating execution plan...', reasoningSoFar: '', toolCallSoFar: null }, interrupt: Promise.resolve(() => { }) }) + this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: 'Generating execution plan...', reasoningSoFar: '', toolCallSoFar: null }, interrupt: Promise.resolve(() => { }) }); // Create a promise that resolves when the plan is generated return new Promise((resolve, reject) => { @@ -1686,20 +1694,20 @@ Output ONLY the JSON, no other text. Start with { and end with }.` separateSystemMessage: undefined, onText: ({ fullText }) => { // Don't show raw JSON to user - just show "Generating plan..." - this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: 'Generating execution plan...', reasoningSoFar: '', toolCallSoFar: null }, interrupt: Promise.resolve(() => { if (llmCancelToken) this._llmMessageService.abort(llmCancelToken) }) }) + this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: 'Generating execution plan...', reasoningSoFar: '', toolCallSoFar: null }, interrupt: Promise.resolve(() => { if (llmCancelToken) { this._llmMessageService.abort(llmCancelToken); } }) }); }, onFinalMessage: async ({ fullText }) => { // Parse plan from LLM response try { // Try to extract JSON from response - const jsonMatch = fullText.match(/\{[\s\S]*\}/) + const jsonMatch = fullText.match(/\{[\s\S]*\}/); if (jsonMatch) { - const planData = JSON.parse(jsonMatch[0]) + const planData = JSON.parse(jsonMatch[0]); const planMessage: PlanMessage = { role: 'plan', type: 'agent_plan', summary: planData.summary || 'Execution plan', - steps: (planData.steps || []).map((step: any, idx: number) => ({ + steps: (planData.steps || []).map((step: unknown, idx: number) => ({ stepNumber: step.stepNumber || idx + 1, description: step.description || `Step ${idx + 1}`, tools: step.tools || [], @@ -1707,16 +1715,16 @@ Output ONLY the JSON, no other text. Start with { and end with }.` status: 'queued' as StepStatus })), approvalState: 'pending' - } + }; // Add plan to thread (DO NOT add assistant message - hide the raw JSON) - this._addMessageToThread(threadId, planMessage) + this._addMessageToThread(threadId, planMessage); // CRITICAL: Invalidate cache immediately so subsequent checks see the new plan - this._planCache.delete(threadId) + this._planCache.delete(threadId); // CRITICAL: Stop execution immediately - set state to idle (don't abort which adds messages) // NOTE: The flag will be checked in the main execution loop - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - resolve() // Resolve when plan is successfully added + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + resolve(); // Resolve when plan is successfully added } else { // Failed to parse - add as assistant message explaining we couldn't parse this._addMessageToThread(threadId, { @@ -1724,77 +1732,76 @@ Output ONLY the JSON, no other text. Start with { and end with }.` displayContent: 'I attempted to create a plan but had difficulty parsing it. Proceeding with direct execution...\n\n' + fullText, reasoning: '', anthropicReasoning: null - }) - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - resolve() // Still resolve - let normal execution continue + }); + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + resolve(); // Still resolve - let normal execution continue } } catch (parseError) { - console.error('Failed to parse plan from LLM:', parseError) + console.error('Failed to parse plan from LLM:', parseError); // Add as assistant message this._addMessageToThread(threadId, { role: 'assistant', displayContent: 'I attempted to create a plan but encountered an error. Proceeding with direct execution...\n\n' + fullText, reasoning: '', anthropicReasoning: null - }) - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - resolve() // Still resolve - let normal execution continue + }); + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + resolve(); // Still resolve - let normal execution continue } }, onError: async (error) => { - this._setStreamState(threadId, { isRunning: undefined, error }) - reject(error) + this._setStreamState(threadId, { isRunning: undefined, error }); + reject(error); }, onAbort: () => { - this._setStreamState(threadId, undefined) - reject(new Error('Plan generation aborted')) + this._setStreamState(threadId, undefined); + reject(new Error('Plan generation aborted')); }, - }) + }); if (!llmCancelToken) { - this._setStreamState(threadId, { isRunning: undefined, error: { message: 'Failed to generate plan', fullError: null } }) - reject(new Error('Failed to start plan generation')) + this._setStreamState(threadId, { isRunning: undefined, error: { message: 'Failed to generate plan', fullError: null } }); + reject(new Error('Failed to start plan generation')); } } catch (error) { - this._setStreamState(threadId, { isRunning: undefined, error: { message: 'Error generating plan', fullError: error instanceof Error ? error : null } }) - reject(error) + this._setStreamState(threadId, { isRunning: undefined, error: { message: 'Error generating plan', fullError: error instanceof Error ? error : null } }); + reject(error); } - }) + }); } async abortRunning(threadId: string) { - const thread = this.state.allThreads[threadId] - if (!thread) return // should never happen + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } // should never happen // add assistant message if (this.streamState[threadId]?.isRunning === 'LLM') { - const { displayContentSoFar, reasoningSoFar, toolCallSoFar } = this.streamState[threadId].llmInfo - this._addMessageToThread(threadId, { role: 'assistant', displayContent: displayContentSoFar, reasoning: reasoningSoFar, anthropicReasoning: null }) - if (toolCallSoFar) this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name, mcpServerName: this._computeMCPServerOfToolName(toolCallSoFar.name) }) + const { displayContentSoFar, reasoningSoFar, toolCallSoFar } = this.streamState[threadId].llmInfo; + this._addMessageToThread(threadId, { role: 'assistant', displayContent: displayContentSoFar, reasoning: reasoningSoFar, anthropicReasoning: null }); + if (toolCallSoFar) { this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name, mcpServerName: this._computeMCPServerOfToolName(toolCallSoFar.name) }); } } // add tool that's running else if (this.streamState[threadId]?.isRunning === 'tool') { - const { toolName, toolParams, id, content: content_, rawParams, mcpServerName } = this.streamState[threadId].toolInfo - const content = content_ || this.toolErrMsgs.interrupted - this._updateLatestTool(threadId, { role: 'tool', name: toolName, params: toolParams, id, content, rawParams, type: 'rejected', result: null, mcpServerName }) + const { toolName, toolParams, id, content: content_, rawParams, mcpServerName } = this.streamState[threadId].toolInfo; + const content = content_ || this.toolErrMsgs.interrupted; + this._updateLatestTool(threadId, { role: 'tool', name: toolName, params: toolParams, id, content, rawParams, type: 'rejected', result: null, mcpServerName }); } // reject the tool for the user if relevant else if (this.streamState[threadId]?.isRunning === 'awaiting_user') { - this.rejectLatestToolRequest(threadId) + this.rejectLatestToolRequest(threadId); } else if (this.streamState[threadId]?.isRunning === 'idle') { // do nothing } - this._addUserCheckpoint({ threadId }) + this._addUserCheckpoint({ threadId }); // interrupt any effects - const interrupt = await this.streamState[threadId]?.interrupt - if (typeof interrupt === 'function') - interrupt() + const interrupt = await this.streamState[threadId]?.interrupt; + if (typeof interrupt === 'function') { interrupt(); } - this._setStreamState(threadId, undefined) + this._setStreamState(threadId, undefined); } @@ -1802,8 +1809,8 @@ Output ONLY the JSON, no other text. Start with { and end with }.` private readonly toolErrMsgs = { rejected: 'Tool call was rejected by the user.', interrupted: 'Tool call was interrupted by the user.', - errWhenStringifying: (error: any) => `Tool call succeeded, but there was an error stringifying the output.\n${getErrorMessage(error)}` - } + errWhenStringifying: (error: unknown) => `Tool call succeeded, but there was an error stringifying the output.\n${getErrorMessage(error)}` + }; // private readonly _currentlyRunningToolInterruptor: { [threadId: string]: (() => void) | undefined } = {} @@ -1814,15 +1821,15 @@ Output ONLY the JSON, no other text. Start with { and end with }.` * Synthesizes a tool call from user intent when the model refuses to use tools. * This ensures Agent Mode works even with models that don't follow tool calling instructions. */ - private _synthesizeToolCallFromIntent(userRequest: string, originalRequest: string): { toolName: string, toolParams: RawToolParamsObj } | null { - const lowerRequest = userRequest.toLowerCase() + private _synthesizeToolCallFromIntent(userRequest: string, originalRequest: string): { toolName: string; toolParams: RawToolParamsObj } | null { + const lowerRequest = userRequest.toLowerCase(); // Extract key terms from the request const extractKeywords = (text: string): string[] => { - const words = text.split(/\s+/).filter(w => w.length > 2) - const stopWords = ['the', 'a', 'an', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'can', 'you', 'add', 'create', 'make', 'do'] - return words.filter(w => !stopWords.includes(w.toLowerCase())).slice(0, 5) - } + const words = text.split(/\s+/).filter(w => w.length > 2); + const stopWords = ['the', 'a', 'an', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'can', 'you', 'add', 'create', 'make', 'do']; + return words.filter(w => !stopWords.includes(w.toLowerCase())).slice(0, 5); + }; // Handle web search queries - expanded patterns if (lowerRequest.includes('search the web') || lowerRequest.includes('search online') || lowerRequest.includes('look up') || @@ -1834,18 +1841,18 @@ Output ONLY the JSON, no other text. Start with { and end with }.` (lowerRequest.includes('search for') && lowerRequest.includes('on the internet')) || (lowerRequest.includes('what is') || lowerRequest.includes('what are') || lowerRequest.includes('who is') || lowerRequest.includes('when did')) && (lowerRequest.includes('latest') || lowerRequest.includes('current') || lowerRequest.includes('recent') || lowerRequest.includes('2024') || lowerRequest.includes('2025'))) { - const keywords = extractKeywords(originalRequest) + const keywords = extractKeywords(originalRequest); // For "tell me what you know about X", extract X - let query = originalRequest + let query = originalRequest; if (lowerRequest.includes('tell me what you know about') || lowerRequest.includes('what do you know about')) { - const aboutMatch = originalRequest.match(/about\s+(.+)/i) || originalRequest.match(/know about\s+(.+)/i) + const aboutMatch = originalRequest.match(/about\s+(.+)/i) || originalRequest.match(/know about\s+(.+)/i); if (aboutMatch) { - query = aboutMatch[1].trim() + query = aboutMatch[1].trim(); } else { - query = keywords.length > 0 ? keywords.join(' ') : originalRequest + query = keywords.length > 0 ? keywords.join(' ') : originalRequest; } } else { - query = keywords.length > 0 ? keywords.join(' ') : originalRequest + query = keywords.length > 0 ? keywords.join(' ') : originalRequest; } return { toolName: 'web_search', @@ -1853,21 +1860,21 @@ Output ONLY the JSON, no other text. Start with { and end with }.` query: query, k: '5' } - } + }; } // Handle URL browsing requests if (lowerRequest.includes('open url') || lowerRequest.includes('fetch url') || lowerRequest.includes('browse url') || lowerRequest.includes('read url') || lowerRequest.includes('get content from') || (lowerRequest.match(/https?:\/\//) && (lowerRequest.includes('read') || lowerRequest.includes('open') || lowerRequest.includes('fetch')))) { - const urlMatch = originalRequest.match(/(https?:\/\/[^\s]+)/i) + const urlMatch = originalRequest.match(/(https?:\/\/[^\s]+)/i); if (urlMatch) { return { toolName: 'browse_url', toolParams: { url: urlMatch[1] } - } + }; } } @@ -1876,33 +1883,33 @@ Output ONLY the JSON, no other text. Start with { and end with }.` (lowerRequest.includes('what') && (lowerRequest.includes('project') || lowerRequest.includes('about'))) || (lowerRequest.includes('how many') && (lowerRequest.includes('endpoint') || lowerRequest.includes('api')))) { // User is asking about the codebase - search for overview files first - const keywords = extractKeywords(originalRequest) - const query = keywords.length > 0 ? keywords.join(' ') : 'readme package.json server api route endpoint' + const keywords = extractKeywords(originalRequest); + const query = keywords.length > 0 ? keywords.join(' ') : 'readme package.json server api route endpoint'; return { toolName: 'search_for_files', toolParams: { query: query } - } + }; } // Determine intent and synthesize appropriate tool call if (lowerRequest.includes('endpoint') || lowerRequest.includes('route') || lowerRequest.includes('api')) { // User wants to add an endpoint - start by searching for server/route files - const keywords = extractKeywords(originalRequest).filter(k => !['dummy', 'endpoint', 'backend'].includes(k.toLowerCase())) - const query = keywords.length > 0 ? keywords.join(' ') : 'server route api endpoint' + const keywords = extractKeywords(originalRequest).filter(k => !['dummy', 'endpoint', 'backend'].includes(k.toLowerCase())); + const query = keywords.length > 0 ? keywords.join(' ') : 'server route api endpoint'; return { toolName: 'search_for_files', toolParams: { query: query } - } + }; } else if (lowerRequest.includes('file') && (lowerRequest.includes('create') || lowerRequest.includes('add') || lowerRequest.includes('make'))) { // User wants to create a file - const keywords = extractKeywords(originalRequest) - const fileName = keywords.find(k => k.includes('.') || k.length > 3) || 'newfile' + const keywords = extractKeywords(originalRequest); + const fileName = keywords.find(k => k.includes('.') || k.length > 3) || 'newfile'; return { toolName: 'create_file_or_folder', @@ -1910,10 +1917,10 @@ Output ONLY the JSON, no other text. Start with { and end with }.` uri: fileName.startsWith('/') ? fileName : `/${fileName}`, type: 'file' } - } + }; } else if (lowerRequest.includes('read') || lowerRequest.includes('show') || lowerRequest.includes('view')) { // User wants to read a file - const fileMatch = originalRequest.match(/([\w\/\.\-]+\.\w+)/i) + const fileMatch = originalRequest.match(/([\w\/\.\-]+\.\w+)/i); if (fileMatch) { return { toolName: 'read_file', @@ -1922,27 +1929,27 @@ Output ONLY the JSON, no other text. Start with { and end with }.` start_line: '1', end_line: '100' } - } + }; } } else if (lowerRequest.includes('edit') || lowerRequest.includes('modify') || lowerRequest.includes('change') || lowerRequest.includes('update')) { // User wants to edit a file - first need to find/read it - const keywords = extractKeywords(originalRequest) + const keywords = extractKeywords(originalRequest); return { toolName: 'search_for_files', toolParams: { query: keywords.join(' ') || 'file' } - } + }; } // Default: search for relevant files based on request - const keywords = extractKeywords(originalRequest) + const keywords = extractKeywords(originalRequest); return { toolName: 'search_for_files', toolParams: { query: keywords.join(' ') || originalRequest.slice(0, 50) } - } + }; } private async _buildEditContext( @@ -2043,7 +2050,7 @@ Output ONLY the JSON, no other text. Start with { and end with }.` for (let i = thread.messages.length - 1; i >= 0; i--) { const msg = thread.messages[i]; if (msg.role === 'assistant' && 'modelSelection' in msg) { - modelSelection = (msg as any).modelSelection; + modelSelection = (msg as unknown).modelSelection; break; } } @@ -2078,9 +2085,9 @@ Output ONLY the JSON, no other text. Start with { and end with }.` ): void { const fileName = editContext.uri.path.split('/').pop() || editContext.uri.path; const operationLabel = toolName === 'rewrite_file' ? 'rewritten' : - toolName === 'edit_file' ? 'edited' : - toolName === 'create_file_or_folder' ? 'created' : - 'modified'; + toolName === 'edit_file' ? 'edited' : + toolName === 'create_file_or_folder' ? 'created' : + 'modified'; // Show brief, non-intrusive notification // Not sticky, auto-dismisses after a few seconds @@ -2120,40 +2127,40 @@ Output ONLY the JSON, no other text. Start with { and end with }.` toolName: ToolName, toolId: string, mcpServerName: string | undefined, - opts: { preapproved: true, unvalidatedToolParams: RawToolParamsObj, validatedParams: ToolCallParams } | { preapproved: false, unvalidatedToolParams: RawToolParamsObj }, - ): Promise<{ awaitingUserApproval?: boolean, interrupted?: boolean }> => { + opts: { preapproved: true; unvalidatedToolParams: RawToolParamsObj; validatedParams: ToolCallParams } | { preapproved: false; unvalidatedToolParams: RawToolParamsObj }, + ): Promise<{ awaitingUserApproval?: boolean; interrupted?: boolean }> => { // compute these below - let toolParams: ToolCallParams - let toolResult: ToolResult - let toolResultStr: string + let toolParams: ToolCallParams; + let toolResult: ToolResult; + let toolResultStr: string; // Check if it's a built-in tool - const isBuiltInTool = isABuiltinToolName(toolName) + const isBuiltInTool = isABuiltinToolName(toolName); if (!opts.preapproved) { // skip this if pre-approved // 1. validate tool params try { if (isBuiltInTool) { - const params = this._toolsService.validateParams[toolName](opts.unvalidatedToolParams) - toolParams = params + const params = this._toolsService.validateParams[toolName](opts.unvalidatedToolParams); + toolParams = params; } else { - toolParams = opts.unvalidatedToolParams + toolParams = opts.unvalidatedToolParams; } } catch (error) { - const errorMessage = getErrorMessage(error) - this._addMessageToThread(threadId, { role: 'tool', type: 'invalid_params', rawParams: opts.unvalidatedToolParams, result: null, name: toolName, content: errorMessage, id: toolId, mcpServerName }) - return {} + const errorMessage = getErrorMessage(error); + this._addMessageToThread(threadId, { role: 'tool', type: 'invalid_params', rawParams: opts.unvalidatedToolParams, result: null, name: toolName, content: errorMessage, id: toolId, mcpServerName }); + return {}; } // once validated, add checkpoint for edit - if (toolName === 'edit_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as BuiltinToolCallParams['edit_file']).uri }) } - if (toolName === 'rewrite_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as BuiltinToolCallParams['rewrite_file']).uri }) } + if (toolName === 'edit_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as BuiltinToolCallParams['edit_file']).uri }); } + if (toolName === 'rewrite_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as BuiltinToolCallParams['rewrite_file']).uri }); } // 2. if tool requires approval, break from the loop, awaiting approval - const approvalType = isBuiltInTool ? approvalTypeOfBuiltinToolName[toolName] : 'MCP tools' + const approvalType = isBuiltInTool ? approvalTypeOfBuiltinToolName[toolName] : 'MCP tools'; if (approvalType) { // Check YOLO mode for edit operations const isEditOperation = isBuiltInTool && ( @@ -2254,40 +2261,40 @@ Output ONLY the JSON, no other text. Start with { and end with }.` }); if (!shouldAutoApprove) { - return { awaitingUserApproval: true } + return { awaitingUserApproval: true }; } } } else { - toolParams = opts.validatedParams + toolParams = opts.validatedParams; } // Check for duplicate read_file calls after validation but before execution if (toolName === 'read_file' && isBuiltInTool) { - const readFileParams = toolParams as BuiltinToolCallParams['read_file'] - const cacheKey = `${readFileParams.uri.fsPath}|${readFileParams.startLine ?? 'null'}|${readFileParams.endLine ?? 'null'}|${readFileParams.pageNumber ?? 1}` + const readFileParams = toolParams as BuiltinToolCallParams['read_file']; + const cacheKey = `${readFileParams.uri.fsPath}|${readFileParams.startLine ?? 'null'}|${readFileParams.endLine ?? 'null'}|${readFileParams.pageNumber ?? 1}`; // Check cache - let threadCache = this._fileReadCache.get(threadId) + let threadCache = this._fileReadCache.get(threadId); if (!threadCache) { - threadCache = new Map() - this._fileReadCache.set(threadId, threadCache) + threadCache = new Map(); + this._fileReadCache.set(threadId, threadCache); } - const cachedResult = threadCache.get(cacheKey) + const cachedResult = threadCache.get(cacheKey); if (cachedResult) { // Found cached result - reuse it instead of reading again // Update LRU: move to end (most recently used) - const lruList = this._fileReadCacheLRU.get(threadId) || [] - const lruIndex = lruList.indexOf(cacheKey) + const lruList = this._fileReadCacheLRU.get(threadId) || []; + const lruIndex = lruList.indexOf(cacheKey); if (lruIndex >= 0) { - lruList.splice(lruIndex, 1) + lruList.splice(lruIndex, 1); } - lruList.push(cacheKey) - this._fileReadCacheLRU.set(threadId, lruList) + lruList.push(cacheKey); + this._fileReadCacheLRU.set(threadId, lruList); - toolResult = cachedResult as ToolResult - toolResultStr = this._toolsService.stringOfResult['read_file'](readFileParams, cachedResult) + toolResult = cachedResult as ToolResult; + toolResultStr = this._toolsService.stringOfResult['read_file'](readFileParams, cachedResult); // Add cached result to thread (mark as cached for transparency) this._updateLatestTool(threadId, { @@ -2300,8 +2307,8 @@ Output ONLY the JSON, no other text. Start with { and end with }.` id: toolId, rawParams: opts.unvalidatedToolParams, mcpServerName - }) - return {} + }); + return {}; } } @@ -2312,134 +2319,134 @@ Output ONLY the JSON, no other text. Start with { and end with }.` // 3. call the tool // this._setStreamState(threadId, { isRunning: 'tool' }, 'merge') - const runningTool = { role: 'tool', type: 'running_now', name: toolName, params: toolParams, content: '(value not received yet...)', result: null, id: toolId, rawParams: opts.unvalidatedToolParams, mcpServerName } as const - this._updateLatestTool(threadId, runningTool) + const runningTool = { role: 'tool', type: 'running_now', name: toolName, params: toolParams, content: '(value not received yet...)', result: null, id: toolId, rawParams: opts.unvalidatedToolParams, mcpServerName } as const; + this._updateLatestTool(threadId, runningTool); - let interrupted = false - let resolveInterruptor: (r: () => void) => void = () => { } - const interruptorPromise = new Promise<() => void>(res => { resolveInterruptor = res }) + let interrupted = false; + let resolveInterruptor: (r: () => void) => void = () => { }; + const interruptorPromise = new Promise<() => void>(res => { resolveInterruptor = res; }); try { // set stream state - this._setStreamState(threadId, { isRunning: 'tool', interrupt: interruptorPromise, toolInfo: { toolName, toolParams, id: toolId, content: 'interrupted...', rawParams: opts.unvalidatedToolParams, mcpServerName } }) + this._setStreamState(threadId, { isRunning: 'tool', interrupt: interruptorPromise, toolInfo: { toolName, toolParams, id: toolId, content: 'interrupted...', rawParams: opts.unvalidatedToolParams, mcpServerName } }); if (isBuiltInTool) { - const { result, interruptTool } = await this._toolsService.callTool[toolName](toolParams as any) - const interruptor = () => { interrupted = true; interruptTool?.() } - resolveInterruptor(interruptor) + const { result, interruptTool } = await this._toolsService.callTool[toolName](toolParams as unknown); + const interruptor = () => { interrupted = true; interruptTool?.(); }; + resolveInterruptor(interruptor); - toolResult = await result + toolResult = await result; } else { - const mcpTools = this._mcpService.getMCPTools() - const mcpTool = mcpTools?.find(t => t.name === toolName) - if (!mcpTool) { throw new Error(`MCP tool ${toolName} not found`) } + const mcpTools = this._mcpService.getMCPTools(); + const mcpTool = mcpTools?.find(t => t.name === toolName); + if (!mcpTool) { throw new Error(`MCP tool ${toolName} not found`); } - resolveInterruptor(() => { }) + resolveInterruptor(() => { }); toolResult = (await this._mcpService.callMCPTool({ serverName: mcpTool.mcpServerName ?? 'unknown_mcp_server', toolName: toolName, params: toolParams - })).result + })).result; } - if (interrupted) { return { interrupted: true } } // the tool result is added where we interrupt, not here + if (interrupted) { return { interrupted: true }; } // the tool result is added where we interrupt, not here } catch (error) { - resolveInterruptor(() => { }) // resolve for the sake of it - if (interrupted) { return { interrupted: true } } // the tool result is added where we interrupt, not here + resolveInterruptor(() => { }); // resolve for the sake of it + if (interrupted) { return { interrupted: true }; } // the tool result is added where we interrupt, not here - const errorMessage = getErrorMessage(error) - this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams, mcpServerName }) - return {} + const errorMessage = getErrorMessage(error); + this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams, mcpServerName }); + return {}; } // 4. stringify the result to give to the LLM try { if (isBuiltInTool) { - toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any) + toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as unknown, toolResult as unknown); } // For MCP tools, handle the result based on its type else { - toolResultStr = this._mcpService.stringifyResult(toolResult as RawMCPToolCall) + toolResultStr = this._mcpService.stringifyResult(toolResult as RawMCPToolCall); } } catch (error) { - const errorMessage = this.toolErrMsgs.errWhenStringifying(error) - this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams, mcpServerName }) - return {} + const errorMessage = this.toolErrMsgs.errWhenStringifying(error); + this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams, mcpServerName }); + return {}; } // 5. add to history and keep going - this._updateLatestTool(threadId, { role: 'tool', type: 'success', params: toolParams, result: toolResult, name: toolName, content: toolResultStr, id: toolId, rawParams: opts.unvalidatedToolParams, mcpServerName }) + this._updateLatestTool(threadId, { role: 'tool', type: 'success', params: toolParams, result: toolResult, name: toolName, content: toolResultStr, id: toolId, rawParams: opts.unvalidatedToolParams, mcpServerName }); // Cache read_file results to prevent duplicate reads if (toolName === 'read_file' && isBuiltInTool) { - const readFileParams = toolParams as BuiltinToolCallParams['read_file'] - const readFileResult = toolResult as BuiltinToolResultType['read_file'] - const cacheKey = `${readFileParams.uri.fsPath}|${readFileParams.startLine ?? 'null'}|${readFileParams.endLine ?? 'null'}|${readFileParams.pageNumber ?? 1}` + const readFileParams = toolParams as BuiltinToolCallParams['read_file']; + const readFileResult = toolResult as BuiltinToolResultType['read_file']; + const cacheKey = `${readFileParams.uri.fsPath}|${readFileParams.startLine ?? 'null'}|${readFileParams.endLine ?? 'null'}|${readFileParams.pageNumber ?? 1}`; - let threadCache = this._fileReadCache.get(threadId) + let threadCache = this._fileReadCache.get(threadId); if (!threadCache) { - threadCache = new Map() - this._fileReadCache.set(threadId, threadCache) + threadCache = new Map(); + this._fileReadCache.set(threadId, threadCache); } // Get or create LRU list for this thread - let lruList = this._fileReadCacheLRU.get(threadId) + let lruList = this._fileReadCacheLRU.get(threadId); if (!lruList) { - lruList = [] - this._fileReadCacheLRU.set(threadId, lruList) + lruList = []; + this._fileReadCacheLRU.set(threadId, lruList); } // If key already exists, remove from LRU list (will be re-added at end) - const existingIndex = lruList.indexOf(cacheKey) + const existingIndex = lruList.indexOf(cacheKey); if (existingIndex >= 0) { - lruList.splice(existingIndex, 1) + lruList.splice(existingIndex, 1); } // Add to end of LRU list (most recently used) - lruList.push(cacheKey) + lruList.push(cacheKey); // Enforce cache size limit with LRU eviction if (lruList.length > ChatThreadService.MAX_FILE_READ_CACHE_ENTRIES_PER_THREAD) { // Remove oldest entry (first in list) - const oldestKey = lruList.shift()! - threadCache.delete(oldestKey) + const oldestKey = lruList.shift()!; + threadCache.delete(oldestKey); } - threadCache.set(cacheKey, readFileResult) + threadCache.set(cacheKey, readFileResult); } // Invalidate cache when files are modified or deleted if ((toolName === 'edit_file' || toolName === 'rewrite_file' || toolName === 'delete_file_or_folder') && isBuiltInTool) { - const fileParams = toolParams as BuiltinToolCallParams['edit_file'] | BuiltinToolCallParams['rewrite_file'] | BuiltinToolCallParams['delete_file_or_folder'] - const fileUri = fileParams.uri - const threadCache = this._fileReadCache.get(threadId) - const lruList = this._fileReadCacheLRU.get(threadId) + const fileParams = toolParams as BuiltinToolCallParams['edit_file'] | BuiltinToolCallParams['rewrite_file'] | BuiltinToolCallParams['delete_file_or_folder']; + const fileUri = fileParams.uri; + const threadCache = this._fileReadCache.get(threadId); + const lruList = this._fileReadCacheLRU.get(threadId); if (threadCache) { // Remove all cache entries for this file (any line range/page) - const keysToDelete: string[] = [] + const keysToDelete: string[] = []; for (const [cacheKey] of threadCache.entries()) { if (cacheKey.startsWith(fileUri.fsPath + '|')) { - keysToDelete.push(cacheKey) - threadCache.delete(cacheKey) + keysToDelete.push(cacheKey); + threadCache.delete(cacheKey); } } // Also remove from LRU list if (lruList) { for (const key of keysToDelete) { - const lruIndex = lruList.indexOf(key) + const lruIndex = lruList.indexOf(key); if (lruIndex >= 0) { - lruList.splice(lruIndex, 1) + lruList.splice(lruIndex, 1); } } } } } - return {} + return {}; }; @@ -2454,98 +2461,98 @@ Output ONLY the JSON, no other text. Start with { and end with }.` isAutoMode, repoIndexerPromise, }: { - threadId: string, - modelSelection: ModelSelection | null, - modelSelectionOptions: ModelSelectionOptions | undefined, - callThisToolFirst?: ToolMessage & { type: 'tool_request' }, - earlyRequestId?: string, - isAutoMode?: boolean, - repoIndexerPromise?: Promise<{ results: string[], metrics: any } | null>, + threadId: string; + modelSelection: ModelSelection | null; + modelSelectionOptions: ModelSelectionOptions | undefined; + callThisToolFirst?: ToolMessage & { type: 'tool_request' }; + earlyRequestId?: string; + isAutoMode?: boolean; + repoIndexerPromise?: Promise<{ results: string[]; metrics: unknown } | null>; }) { // CRITICAL: Create a flag to stop execution immediately when plan is generated // NOTE: This flag is reset when plan is approved/executing to allow execution to proceed - let planWasGenerated = false + let planWasGenerated = false; const checkPlanGenerated = () => { // Fast path: if flag is already set, check if plan is still pending if (planWasGenerated) { // Force refresh to check if plan was approved since flag was set - const plan = this._getCurrentPlan(threadId, true) + const plan = this._getCurrentPlan(threadId, true); if (plan && plan.plan.approvalState === 'pending') { - return true // Still pending + return true; // Still pending } // Plan was approved - reset flag to allow execution - planWasGenerated = false - return false + planWasGenerated = false; + return false; } // Use cached check first for performance - only force refresh if we suspect state changed - const plan = this._getCurrentPlan(threadId, false) // Use cache for performance + const plan = this._getCurrentPlan(threadId, false); // Use cache for performance if (plan && plan.plan.approvalState === 'pending') { // Check if this plan was created during this execution session // We check the plan's message index - if it's near the end of messages, it's recent - const thread = this.state.allThreads[threadId] + const thread = this.state.allThreads[threadId]; if (thread) { - const totalMessages = thread.messages.length - const planIdx = plan.planIdx + const totalMessages = thread.messages.length; + const planIdx = plan.planIdx; // If plan is in the last 10 messages, consider it recent (likely from this session) // This is safer than using timestamps which might not exist - const isRecentPlan = (totalMessages - planIdx) <= 10 + const isRecentPlan = (totalMessages - planIdx) <= 10; if (isRecentPlan) { - planWasGenerated = true - return true + planWasGenerated = true; + return true; } } } - return false - } + return false; + }; - let interruptedWhenIdle = false - const idleInterruptor = Promise.resolve(() => { interruptedWhenIdle = true }) + let interruptedWhenIdle = false; + const idleInterruptor = Promise.resolve(() => { interruptedWhenIdle = true; }); // _runToolCall does not need setStreamState({idle}) before it, but it needs it after it. (handles its own setStreamState) // above just defines helpers, below starts the actual function - const { chatMode } = this._settingsService.state.globalSettings // should not change as we loop even if user changes it, so it goes here - const { overridesOfModel } = this._settingsService.state + const { chatMode } = this._settingsService.state.globalSettings; // should not change as we loop even if user changes it, so it goes here + const { overridesOfModel } = this._settingsService.state; - let nMessagesSent = 0 - let shouldSendAnotherMessage = true - let isRunningWhenEnd: IsRunningType = undefined - let filesReadInQuery = 0 // Track number of files read to prevent excessive reads + let nMessagesSent = 0; + let shouldSendAnotherMessage = true; + let isRunningWhenEnd: IsRunningType = undefined; + let filesReadInQuery = 0; // Track number of files read to prevent excessive reads // PERFORMANCE: Check for plan ONCE at start, not on every tool call // Only do plan tracking if an active plan exists - let activePlanTracking: { planInfo: { plan: PlanMessage, planIdx: number }, currentStep: { plan: PlanMessage, planIdx: number, step: PlanStep, stepIdx: number } | undefined } | undefined + let activePlanTracking: { planInfo: { plan: PlanMessage; planIdx: number }; currentStep: { plan: PlanMessage; planIdx: number; step: PlanStep; stepIdx: number } | undefined } | undefined; // Check if we should generate a plan for complex tasks - const existingPlanInfo = this._getCurrentPlan(threadId, false) // Use cache + const existingPlanInfo = this._getCurrentPlan(threadId, false); // Use cache if (!existingPlanInfo) { // No existing plan - check if we should generate one - const shouldGeneratePlan = this._shouldGeneratePlan(threadId) + const shouldGeneratePlan = this._shouldGeneratePlan(threadId); if (shouldGeneratePlan) { - await this._generatePlanFromUserRequest(threadId, modelSelection, modelSelectionOptions) + await this._generatePlanFromUserRequest(threadId, modelSelection, modelSelectionOptions); // CRITICAL: Force cache refresh ONLY here after plan generation - this._planCache.delete(threadId) - const planAfterGen = this._getCurrentPlan(threadId, true) // Force refresh + this._planCache.delete(threadId); + const planAfterGen = this._getCurrentPlan(threadId, true); // Force refresh if (planAfterGen && planAfterGen.plan.approvalState === 'pending') { - planWasGenerated = true + planWasGenerated = true; // Plan generated, wait for user approval - don't execute yet - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } } } else { // Existing plan found - check if it's pending (old plans might be completed/aborted) if (existingPlanInfo.plan.approvalState === 'pending') { - planWasGenerated = true - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + planWasGenerated = true; + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } } // CRITICAL: Force refresh after approval to get latest plan state (cache was invalidated) - let planInfo = this._getCurrentPlan(threadId, true) + let planInfo = this._getCurrentPlan(threadId, true); if (planInfo && (planInfo.plan.approvalState === 'approved' || planInfo.plan.approvalState === 'executing')) { // Only initialize tracking if plan is approved/executing if (planInfo.plan.approvalState === 'approved') { @@ -2554,144 +2561,144 @@ Output ONLY the JSON, no other text. Start with { and end with }.` ...planInfo.plan, approvalState: 'executing', executionStartTime: Date.now() - } - this._editMessageInThread(threadId, planInfo.planIdx, updatedPlan) + }; + this._editMessageInThread(threadId, planInfo.planIdx, updatedPlan); // Invalidate cache after update and refresh planInfo to get updated plan - this._planCache.delete(threadId) - const refreshed = this._getCurrentPlan(threadId, true) // Refresh to get updated plan + this._planCache.delete(threadId); + const refreshed = this._getCurrentPlan(threadId, true); // Refresh to get updated plan if (refreshed) { - planInfo = refreshed + planInfo = refreshed; } } // Get current step once - const currentStep = this._getCurrentStep(threadId, true) // Force refresh to get latest step state + const currentStep = this._getCurrentStep(threadId, true); // Force refresh to get latest step state if (currentStep && currentStep.step.status === 'queued') { // Start next step - this updates the step status to 'running' and invalidates cache - this._startNextStep(threadId) + this._startNextStep(threadId); // Refresh both plan and step after starting to get updated state - this._planCache.delete(threadId) - const refreshedPlanInfo = this._getCurrentPlan(threadId, true) + this._planCache.delete(threadId); + const refreshedPlanInfo = this._getCurrentPlan(threadId, true); // Ensure we have a valid planInfo before assigning if (refreshedPlanInfo) { activePlanTracking = { planInfo: refreshedPlanInfo, currentStep: this._getCurrentStep(threadId, true) // Force refresh to see 'running' status - } + }; } else if (planInfo) { // Fallback to original planInfo if refresh failed (shouldn't happen, but type-safe) activePlanTracking = { planInfo, currentStep: this._getCurrentStep(threadId, true) - } + }; } } else { // planInfo is guaranteed to be defined here due to the outer if check activePlanTracking = { planInfo, currentStep - } + }; } } // Helper to update current step after operations const refreshPlanStep = () => { if (activePlanTracking) { - activePlanTracking.currentStep = this._getCurrentStep(threadId, true) + activePlanTracking.currentStep = this._getCurrentStep(threadId, true); } - } + }; // CRITICAL: Check for pending plan before executing any tools // Use fast check (relies on flag and cached plan check) if (checkPlanGenerated()) { // Plan is pending approval - stop execution - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } // before enter loop, call tool if (callThisToolFirst) { // Double-check plan status before executing (fast check) if (checkPlanGenerated()) { - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } if (activePlanTracking?.currentStep) { - this._linkToolCallToStepInternal(threadId, callThisToolFirst.id, activePlanTracking.currentStep) + this._linkToolCallToStepInternal(threadId, callThisToolFirst.id, activePlanTracking.currentStep); } - const { interrupted } = await this._runToolCall(threadId, callThisToolFirst.name, callThisToolFirst.id, callThisToolFirst.mcpServerName, { preapproved: true, unvalidatedToolParams: callThisToolFirst.rawParams, validatedParams: callThisToolFirst.params }) + const { interrupted } = await this._runToolCall(threadId, callThisToolFirst.name, callThisToolFirst.id, callThisToolFirst.mcpServerName, { preapproved: true, unvalidatedToolParams: callThisToolFirst.rawParams, validatedParams: callThisToolFirst.params }); if (interrupted) { - this._setStreamState(threadId, undefined) - this._addUserCheckpoint({ threadId }) + this._setStreamState(threadId, undefined); + this._addUserCheckpoint({ threadId }); if (activePlanTracking?.currentStep) { - this._markStepCompletedInternal(threadId, activePlanTracking.currentStep, false, 'Interrupted by user') - refreshPlanStep() + this._markStepCompletedInternal(threadId, activePlanTracking.currentStep, false, 'Interrupted by user'); + refreshPlanStep(); } } else { // Mark step as completed on success if (activePlanTracking?.currentStep) { - this._markStepCompletedInternal(threadId, activePlanTracking.currentStep, true) + this._markStepCompletedInternal(threadId, activePlanTracking.currentStep, true); // Start next step - this._startNextStep(threadId) - refreshPlanStep() + this._startNextStep(threadId); + refreshPlanStep(); } } } - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) // just decorative, for clarity + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); // just decorative, for clarity // tool use loop while (shouldSendAnotherMessage) { // CRITICAL: Check for maximum iterations to prevent infinite loops if (nMessagesSent >= MAX_AGENT_LOOP_ITERATIONS) { - this._notificationService.warn(`Agent loop reached maximum iterations (${MAX_AGENT_LOOP_ITERATIONS}). Stopping to prevent infinite loop.`) - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._notificationService.warn(`Agent loop reached maximum iterations (${MAX_AGENT_LOOP_ITERATIONS}). Stopping to prevent infinite loop.`); + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } // CRITICAL: Check stream state first - if execution was interrupted/aborted, stop immediately - const currentStreamState = this.streamState[threadId] + const currentStreamState = this.streamState[threadId]; if (!currentStreamState || currentStreamState.isRunning === undefined) { // Execution was aborted/interrupted - stop immediately - return + return; } // CRITICAL: Check for pending plan before each iteration - don't execute tools if plan is pending approval // Use fast check (flag + cached check) - only force refresh every few iterations to save performance if (checkPlanGenerated()) { // Plan is pending approval - stop execution and wait - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } // false by default each iteration - shouldSendAnotherMessage = false - isRunningWhenEnd = undefined - nMessagesSent += 1 + shouldSendAnotherMessage = false; + isRunningWhenEnd = undefined; + nMessagesSent += 1; - this._setStreamState(threadId, { isRunning: 'idle', interrupt: idleInterruptor }) + this._setStreamState(threadId, { isRunning: 'idle', interrupt: idleInterruptor }); - const chatMessages = this.state.allThreads[threadId]?.messages ?? [] + const chatMessages = this.state.allThreads[threadId]?.messages ?? []; // Check if we've already synthesized a tool for this original request (prevent infinite loops) - const allUserMessages = chatMessages.filter(m => m.role === 'user') + const allUserMessages = chatMessages.filter(m => m.role === 'user'); const originalUserMessage = allUserMessages.find(m => !m.displayContent?.includes('⚠️ CRITICAL') && !m.displayContent?.includes('You did not use tools') - ) - const originalRequestId = originalUserMessage ? `${originalUserMessage.displayContent}` : null + ); + const originalRequestId = originalUserMessage ? `${originalUserMessage.displayContent}` : null; // Track if we've already synthesized a tool for this request const hasSynthesizedForRequest = originalRequestId && chatMessages.some((msg, idx) => { if (msg.role === 'assistant' && msg.displayContent?.includes('Let me start by')) { // Check if there's a tool message right after this assistant message - const nextMsg = chatMessages[idx + 1] - return nextMsg?.role === 'tool' + const nextMsg = chatMessages[idx + 1]; + return nextMsg?.role === 'tool'; } - return false - }) + return false; + }); // Preprocess images through QA pipeline if present let preprocessedMessages = chatMessages; @@ -2714,8 +2721,8 @@ Output ONLY the JSON, no other text. Start with { and end with }.` if (settings.imageQADevMode && preprocessed.qaResponse) { console.log('[ImageQA] Pipeline response:', { confidence: preprocessed.qaResponse.confidence, - needsLLM: !!(preprocessed.qaResponse as any)._needsLLM, - needsVLM: !!(preprocessed.qaResponse as any)._needsVLM, + needsLLM: !!(preprocessed.qaResponse as unknown)._needsLLM, + needsVLM: !!(preprocessed.qaResponse as unknown)._needsVLM, answer: preprocessed.qaResponse.answer?.substring(0, 100), }); } @@ -2745,8 +2752,8 @@ Output ONLY the JSON, no other text. Start with { and end with }.` // CRITICAL: Check for pending plan BEFORE preparing LLM messages (saves API calls) // checkPlanGenerated() already checks planWasGenerated internally, no need to check twice if (checkPlanGenerated()) { - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } // CRITICAL: Validate modelSelection before preparing messages @@ -2754,127 +2761,143 @@ Output ONLY the JSON, no other text. Start with { and end with }.` // If auto selection failed and returned unresolved 'auto', try fallback if (!modelSelection || (modelSelection.providerName === 'auto' && modelSelection.modelName === 'auto')) { // Try to get fallback model instead of erroring - const fallbackModel = this._getFallbackModel() + const fallbackModel = this._getFallbackModel(); if (fallbackModel) { - modelSelection = fallbackModel + modelSelection = fallbackModel; // Only log to console to avoid notification spam - fallback should work transparently - console.debug('[ChatThreadService] Auto model selection failed, using fallback model:', fallbackModel) + console.debug('[ChatThreadService] Auto model selection failed, using fallback model:', fallbackModel); } else { // Last resort: no models available - this._notificationService.error('No models available. Please configure at least one model provider in settings.') - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._notificationService.error('No models available. Please configure at least one model provider in settings.'); + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } } // Start latency audit tracking (reuse earlyRequestId if provided for router tracking, otherwise generate new) - const finalRequestId = earlyRequestId || generateUuid() - const providerName = modelSelection.providerName - const modelName = modelSelection.modelName + const finalRequestId = earlyRequestId || generateUuid(); + + // CRITICAL: Final validation right before use - defensive programming + // This ensures we never pass invalid modelSelection to the LLM service + if (!modelSelection || isAutoModelSelection(modelSelection)) { + console.error('[ChatThreadService] CRITICAL: Invalid modelSelection detected right before LLM call:', modelSelection); + const fallbackModel = this._getFallbackModel(); + if (fallbackModel) { + modelSelection = fallbackModel; + console.warn('[ChatThreadService] Using emergency fallback model:', fallbackModel); + } else { + this._notificationService.error('No models available. Please configure at least one model provider in settings.'); + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; + } + } + + const providerName = modelSelection.providerName; + const modelName = modelSelection.modelName; // Only start new request if we didn't already start it for router tracking if (!earlyRequestId) { - chatLatencyAudit.startRequest(finalRequestId, providerName, modelName) + chatLatencyAudit.startRequest(finalRequestId, providerName, modelName); // For manual selection, router time is 0 (instant) - chatLatencyAudit.markRouterStart(finalRequestId) - chatLatencyAudit.markRouterEnd(finalRequestId) + chatLatencyAudit.markRouterStart(finalRequestId); + chatLatencyAudit.markRouterEnd(finalRequestId); } else { // Update provider/model info if we started request early for router tracking - const context = chatLatencyAudit.getContext(finalRequestId) + const context = chatLatencyAudit.getContext(finalRequestId); if (context) { - context.providerName = providerName - context.modelName = modelName + context.providerName = providerName; + context.modelName = modelName; } } - chatLatencyAudit.markPromptAssemblyStart(finalRequestId) + chatLatencyAudit.markPromptAssemblyStart(finalRequestId); const { messages, separateSystemMessage } = await this._convertToLLMMessagesService.prepareLLMChatMessages({ chatMessages: preprocessedMessages, modelSelection, chatMode, repoIndexerPromise - }) + }); // CRITICAL: Validate that messages are not empty before sending to API // Empty messages cause "invalid message format" errors if (!messages || messages.length === 0) { - this._notificationService.error('Failed to prepare messages. Please check your message content.') - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._notificationService.error('Failed to prepare messages. Please check your message content.'); + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } // CRITICAL: Check again after async operation (plan might have been added during prep) // Invalidate cache in case plan was added during message prep, then use fast check - this._planCache.delete(threadId) + this._planCache.delete(threadId); if (checkPlanGenerated()) { - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } if (interruptedWhenIdle) { - this._setStreamState(threadId, undefined) - return + this._setStreamState(threadId, undefined); + return; } // Track prompt assembly end and estimate tokens - const estimateTokens = (text: string) => Math.ceil(text.length / 4) + const estimateTokens = (text: string) => Math.ceil(text.length / 4); const promptTokens = messages.reduce((acc, m) => { // Handle Gemini messages (use 'parts' instead of 'content') if ('parts' in m) { return acc + m.parts.reduce((sum: number, part) => { if ('text' in part && typeof part.text === 'string') { - return sum + estimateTokens(part.text) + return sum + estimateTokens(part.text); } else if ('inlineData' in part) { // Rough estimate: ~85 tokens per image + base64 overhead - return sum + 100 + return sum + 100; } - return sum - }, 0) + return sum; + }, 0); } // Handle Anthropic/OpenAI messages (use 'content') if ('content' in m) { if (typeof m.content === 'string') { - return acc + estimateTokens(m.content) + return acc + estimateTokens(m.content); } else if (Array.isArray(m.content)) { // Handle OpenAI format with image_url parts - return acc + m.content.reduce((sum: number, part: any) => { + return acc + m.content.reduce((sum: number, part: unknown) => { if (part.type === 'text') { - return sum + estimateTokens(part.text) + return sum + estimateTokens(part.text); } else if (part.type === 'image_url') { // Rough estimate: ~85 tokens per image + base64 overhead - return sum + 100 + return sum + 100; } - return sum - }, 0) + return sum; + }, 0); } - return acc + estimateTokens(JSON.stringify(m.content)) + return acc + estimateTokens(JSON.stringify(m.content)); } - return acc - }, 0) + return acc; + }, 0); const contextSize = messages.reduce((acc, m) => { // Handle Gemini messages (use 'parts' instead of 'content') if ('parts' in m) { return acc + m.parts.reduce((sum: number, part) => { if ('text' in part && typeof part.text === 'string') { - return sum + part.text.length + return sum + part.text.length; } - return sum - }, 0) + return sum; + }, 0); } // Handle Anthropic/OpenAI messages (use 'content') if ('content' in m) { if (typeof m.content === 'string') { - return acc + m.content.length + return acc + m.content.length; } else if (Array.isArray(m.content)) { - return acc + m.content.reduce((sum: number, part: any) => { - if (part.type === 'text') return sum + part.text.length - return sum - }, 0) + return acc + m.content.reduce((sum: number, part: unknown) => { + if (part.type === 'text') { return sum + part.text.length; } + return sum; + }, 0); } - return acc + JSON.stringify(m.content).length + return acc + JSON.stringify(m.content).length; } - return acc - }, 0) - chatLatencyAudit.markPromptAssemblyEnd(finalRequestId, promptTokens, 0, contextSize, false) + return acc; + }, 0); + chatLatencyAudit.markPromptAssemblyEnd(finalRequestId, promptTokens, 0, contextSize, false); // Audit log: record prompt if (this._auditLogService.isEnabled() && modelSelection) { @@ -2892,34 +2915,34 @@ Output ONLY the JSON, no other text. Start with { and end with }.` }); } - let shouldRetryLLM = true - let nAttempts = 0 - let firstTokenReceived = false + let shouldRetryLLM = true; + let nAttempts = 0; + let firstTokenReceived = false; while (shouldRetryLLM) { - shouldRetryLLM = false - nAttempts += 1 + shouldRetryLLM = false; + nAttempts += 1; type ResTypes = - | { type: 'llmDone', toolCall?: RawToolCallObj, info: { fullText: string, fullReasoning: string, anthropicReasoning: AnthropicReasoning[] | null } } - | { type: 'llmError', error?: { message: string; fullError: Error | null; } } - | { type: 'llmAborted' } + | { type: 'llmDone'; toolCall?: RawToolCallObj; info: { fullText: string; fullReasoning: string; anthropicReasoning: AnthropicReasoning[] | null } } + | { type: 'llmError'; error?: { message: string; fullError: Error | null } } + | { type: 'llmAborted' }; - let resMessageIsDonePromise: (res: ResTypes) => void // resolves when user approves this tool use (or if tool doesn't require approval) - const messageIsDonePromise = new Promise((res, rej) => { resMessageIsDonePromise = res }) + let resMessageIsDonePromise: (res: ResTypes) => void; // resolves when user approves this tool use (or if tool doesn't require approval) + const messageIsDonePromise = new Promise((res, rej) => { resMessageIsDonePromise = res; }); // Track if message is done to prevent late onText updates - let messageIsDone = false + let messageIsDone = false; // Track network request start (when we actually send to the LLM) - chatLatencyAudit.markNetworkStart(finalRequestId) + chatLatencyAudit.markNetworkStart(finalRequestId); // Track network start time for timeout fallback (if no tokens arrive) const networkTimeout = setTimeout(() => { // Fallback: if no tokens arrive within 30s, mark network end anyway - const context = chatLatencyAudit.getContext(finalRequestId) + const context = chatLatencyAudit.getContext(finalRequestId); if (context && !context.networkEndTime) { - chatLatencyAudit.markNetworkEnd(finalRequestId) + chatLatencyAudit.markNetworkEnd(finalRequestId); } - }, 30000) + }, 30000); const llmCancelToken = this._llmMessageService.sendLLMMessage({ messagesType: 'chatMessages', @@ -2933,17 +2956,17 @@ Output ONLY the JSON, no other text. Start with { and end with }.` onText: ({ fullText, fullReasoning, toolCall }) => { // Guard: Don't update stream state if message is already done (prevents late onText calls from requestAnimationFrame) if (messageIsDone) { - return + return; } // Clear timeout once we receive first chunk - clearTimeout(networkTimeout) + clearTimeout(networkTimeout); // Track first token (TTFS) and network end (when we receive first chunk) // Check both fullText and fullReasoning - first token might be in either if (!firstTokenReceived && (fullText.length > 0 || fullReasoning.length > 0)) { - firstTokenReceived = true - chatLatencyAudit.markNetworkEnd(finalRequestId) // Network complete when first token arrives - chatLatencyAudit.markFirstToken(finalRequestId) + firstTokenReceived = true; + chatLatencyAudit.markNetworkEnd(finalRequestId); // Network complete when first token arrives + chatLatencyAudit.markFirstToken(finalRequestId); } // Batch token updates for smooth 60 FPS rendering @@ -2965,46 +2988,46 @@ Output ONLY the JSON, no other text. Start with { and end with }.` requestAnimationFrame(() => { // Guard again: Check if message is done before updating state (prevents race conditions) if (messageIsDone) { - return + return; } // Also check if stream state is still 'LLM' (another guard against late updates) - const currentState = this.streamState[threadId] + const currentState = this.streamState[threadId]; if (currentState?.isRunning !== 'LLM') { - return + return; } // Record render frame for FPS tracking - chatLatencyAudit.recordRenderFrame(finalRequestId) - this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall ?? null }, interrupt: Promise.resolve(() => { if (llmCancelToken) this._llmMessageService.abort(llmCancelToken) }) }) - }) + chatLatencyAudit.recordRenderFrame(finalRequestId); + this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: fullText, reasoningSoFar: fullReasoning, toolCallSoFar: toolCall ?? null }, interrupt: Promise.resolve(() => { if (llmCancelToken) { this._llmMessageService.abort(llmCancelToken); } }) }); + }); }, onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => { // Mark message as done to prevent late onText updates - messageIsDone = true + messageIsDone = true; // Clear timeout - clearTimeout(networkTimeout) + clearTimeout(networkTimeout); // Ensure network end and first token are tracked (fallback for non-streaming responses) // If onText was never called, this is a non-streaming response - treat final message as first token if (!firstTokenReceived) { - chatLatencyAudit.markNetworkEnd(finalRequestId) + chatLatencyAudit.markNetworkEnd(finalRequestId); // For non-streaming responses, the final message IS the first token // Only mark if we actually have content (not an empty response) - const hasContent = (fullText && fullText.length > 0) || (fullReasoning && fullReasoning.length > 0) + const hasContent = (fullText && fullText.length > 0) || (fullReasoning && fullReasoning.length > 0); if (hasContent) { - chatLatencyAudit.markFirstToken(finalRequestId) + chatLatencyAudit.markFirstToken(finalRequestId); } } // Track completion (TTS) and output tokens // Use fullText length, or fallback to reasoning if text is empty const textToCount = fullText || fullReasoning || ''; // More accurate token estimation: account for markdown, code blocks, etc. - const outputTokens = textToCount.length > 0 ? Math.max(1, Math.ceil(textToCount.length / 3.5)) : 0 - chatLatencyAudit.markStreamComplete(finalRequestId, outputTokens) + const outputTokens = textToCount.length > 0 ? Math.max(1, Math.ceil(textToCount.length / 3.5)) : 0; + chatLatencyAudit.markStreamComplete(finalRequestId, outputTokens); // Log metrics for debugging - const metrics = chatLatencyAudit.completeRequest(finalRequestId) + const metrics = chatLatencyAudit.completeRequest(finalRequestId); if (metrics) { - chatLatencyAudit.logMetrics(metrics) + chatLatencyAudit.logMetrics(metrics); } // Audit log: record reply @@ -3024,15 +3047,15 @@ Output ONLY the JSON, no other text. Start with { and end with }.` }); } - resMessageIsDonePromise({ type: 'llmDone', toolCall, info: { fullText, fullReasoning, anthropicReasoning } }) // resolve with tool calls + resMessageIsDonePromise({ type: 'llmDone', toolCall, info: { fullText, fullReasoning, anthropicReasoning } }); // resolve with tool calls }, onError: async (error) => { // Clear timeout - clearTimeout(networkTimeout) + clearTimeout(networkTimeout); // Ensure network end is tracked even on error (idempotent - safe to call multiple times) - chatLatencyAudit.markNetworkEnd(finalRequestId) + chatLatencyAudit.markNetworkEnd(finalRequestId); // Mark stream as complete with 0 tokens on error - chatLatencyAudit.markStreamComplete(finalRequestId, 0) + chatLatencyAudit.markStreamComplete(finalRequestId, 0); // Audit log: record error if (this._auditLogService.isEnabled() && modelSelection) { @@ -3049,79 +3072,78 @@ Output ONLY the JSON, no other text. Start with { and end with }.` }); } - resMessageIsDonePromise({ type: 'llmError', error: error }) + resMessageIsDonePromise({ type: 'llmError', error: error }); }, onAbort: () => { // stop the loop to free up the promise, but don't modify state (already handled by whatever stopped it) - resMessageIsDonePromise({ type: 'llmAborted' }) - this._metricsService.capture('Agent Loop Done (Aborted)', { nMessagesSent, chatMode }) + resMessageIsDonePromise({ type: 'llmAborted' }); + this._metricsService.capture('Agent Loop Done (Aborted)', { nMessagesSent, chatMode }); }, - }) + }); // mark as streaming if (!llmCancelToken) { - this._setStreamState(threadId, { isRunning: undefined, error: { message: 'There was an unexpected error when sending your chat message.', fullError: null } }) - break + this._setStreamState(threadId, { isRunning: undefined, error: { message: 'There was an unexpected error when sending your chat message.', fullError: null } }); + break; } // Update status to show we're waiting for the model response - this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: 'Waiting for model response...', reasoningSoFar: '', toolCallSoFar: null }, interrupt: Promise.resolve(() => this._llmMessageService.abort(llmCancelToken)) }) - const llmRes = await messageIsDonePromise // wait for message to complete + this._setStreamState(threadId, { isRunning: 'LLM', llmInfo: { displayContentSoFar: 'Waiting for model response...', reasoningSoFar: '', toolCallSoFar: null }, interrupt: Promise.resolve(() => this._llmMessageService.abort(llmCancelToken)) }); + const llmRes = await messageIsDonePromise; // wait for message to complete // if something else started running in the meantime if (this.streamState[threadId]?.isRunning !== 'LLM') { // console.log('Chat thread interrupted by a newer chat thread', this.streamState[threadId]?.isRunning) - return + return; } // llm res aborted if (llmRes.type === 'llmAborted') { - this._setStreamState(threadId, undefined) - return + this._setStreamState(threadId, undefined); + return; } // llm res error else if (llmRes.type === 'llmError') { - const { error } = llmRes + const { error } = llmRes; // Check if this is a rate limit error (429) - don't retry these immediately const isRateLimitError = error?.message?.includes('429') || error?.message?.toLowerCase().includes('rate limit') || error?.message?.toLowerCase().includes('tokens per min') || - error?.message?.toLowerCase().includes('tpm') + error?.message?.toLowerCase().includes('tpm'); // For rate limit errors, don't retry - show error immediately if (isRateLimitError) { - const { displayContentSoFar, reasoningSoFar, toolCallSoFar } = this.streamState[threadId].llmInfo - this._addMessageToThread(threadId, { role: 'assistant', displayContent: displayContentSoFar, reasoning: reasoningSoFar, anthropicReasoning: null }) - if (toolCallSoFar) this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name, mcpServerName: this._computeMCPServerOfToolName(toolCallSoFar.name) }) + const { displayContentSoFar, reasoningSoFar, toolCallSoFar } = this.streamState[threadId].llmInfo; + this._addMessageToThread(threadId, { role: 'assistant', displayContent: displayContentSoFar, reasoning: reasoningSoFar, anthropicReasoning: null }); + if (toolCallSoFar) { this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name, mcpServerName: this._computeMCPServerOfToolName(toolCallSoFar.name) }); } - this._setStreamState(threadId, { isRunning: undefined, error }) - this._addUserCheckpoint({ threadId }) - return + this._setStreamState(threadId, { isRunning: undefined, error }); + this._addUserCheckpoint({ threadId }); + return; } // For other errors, retry if we haven't exceeded retry limit if (nAttempts < CHAT_RETRIES) { - shouldRetryLLM = true - this._setStreamState(threadId, { isRunning: 'idle', interrupt: idleInterruptor }) + shouldRetryLLM = true; + this._setStreamState(threadId, { isRunning: 'idle', interrupt: idleInterruptor }); // Exponential backoff: 1s, 2s, 4s (capped at 5s) - const retryDelay = Math.min(INITIAL_RETRY_DELAY * Math.pow(2, nAttempts - 1), MAX_RETRY_DELAY) - await timeout(retryDelay) + const retryDelay = Math.min(INITIAL_RETRY_DELAY * Math.pow(2, nAttempts - 1), MAX_RETRY_DELAY); + await timeout(retryDelay); if (interruptedWhenIdle) { - this._setStreamState(threadId, undefined) - return + this._setStreamState(threadId, undefined); + return; } - else - continue // retry + else { continue; } // retry } // error, but too many attempts else { - const { displayContentSoFar, reasoningSoFar, toolCallSoFar } = this.streamState[threadId].llmInfo - this._addMessageToThread(threadId, { role: 'assistant', displayContent: displayContentSoFar, reasoning: reasoningSoFar, anthropicReasoning: null }) - if (toolCallSoFar) this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name, mcpServerName: this._computeMCPServerOfToolName(toolCallSoFar.name) }) + const { displayContentSoFar, reasoningSoFar, toolCallSoFar } = this.streamState[threadId].llmInfo; + this._addMessageToThread(threadId, { role: 'assistant', displayContent: displayContentSoFar, reasoning: reasoningSoFar, anthropicReasoning: null }); + if (toolCallSoFar) { this._addMessageToThread(threadId, { role: 'interrupted_streaming_tool', name: toolCallSoFar.name, mcpServerName: this._computeMCPServerOfToolName(toolCallSoFar.name) }); } - this._setStreamState(threadId, { isRunning: undefined, error }) - this._addUserCheckpoint({ threadId }) - return + this._setStreamState(threadId, { isRunning: undefined, error }); + this._addUserCheckpoint({ threadId }); + return; } } @@ -3129,45 +3151,45 @@ Output ONLY the JSON, no other text. Start with { and end with }.` // Use fast check - flag should catch most cases if (checkPlanGenerated()) { // Plan is pending approval - stop execution and wait - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } // llm res success - const { toolCall, info } = llmRes + const { toolCall, info } = llmRes; // Track if we synthesized a tool and added a message (to prevent duplicate messages) - let toolSynthesizedAndMessageAdded = false + let toolSynthesizedAndMessageAdded = false; // Detect if Agent Mode should have used tools but didn't // Only synthesize ONCE per original request to prevent infinite loops // Also check if we've already read too many files (prevent infinite read loops) if (chatMode === 'agent' && !toolCall && info.fullText.trim() && !hasSynthesizedForRequest && filesReadInQuery < MAX_FILES_READ_PER_QUERY) { if (originalUserMessage) { - const userRequest = originalUserMessage.displayContent?.toLowerCase() || '' - const actionWords = ['add', 'create', 'edit', 'delete', 'remove', 'update', 'modify', 'change', 'make', 'write', 'build', 'implement', 'fix', 'run', 'execute', 'install', 'setup', 'configure'] - const codebaseQueryWords = ['codebase', 'code base', 'repository', 'repo', 'project', 'endpoint', 'endpoints', 'api', 'route', 'routes', 'files', 'structure', 'architecture', 'what is', 'about'] - const webQueryWords = ['search the web', 'search online', 'check the web', 'check the internet', 'check internet', 'look up', 'google', 'duckduckgo', 'browse url', 'fetch url', 'open url'] + const userRequest = originalUserMessage.displayContent?.toLowerCase() || ''; + const actionWords = ['add', 'create', 'edit', 'delete', 'remove', 'update', 'modify', 'change', 'make', 'write', 'build', 'implement', 'fix', 'run', 'execute', 'install', 'setup', 'configure']; + const codebaseQueryWords = ['codebase', 'code base', 'repository', 'repo', 'project', 'endpoint', 'endpoints', 'api', 'route', 'routes', 'files', 'structure', 'architecture', 'what is', 'about']; + const webQueryWords = ['search the web', 'search online', 'check the web', 'check the internet', 'check internet', 'look up', 'google', 'duckduckgo', 'browse url', 'fetch url', 'open url']; const isActionRequest = actionWords.some(word => userRequest.includes(word)) && !userRequest.startsWith('explain') && !userRequest.startsWith('what') && !userRequest.startsWith('how') && - !userRequest.startsWith('why') + !userRequest.startsWith('why'); // Also treat codebase queries as requiring tools (need to read files to answer accurately) // BUT: If images are present, "what" questions are likely about the image, not the codebase - const hasImages = originalUserMessage.images && originalUserMessage.images.length > 0 + const hasImages = originalUserMessage.images && originalUserMessage.images.length > 0; const isCodebaseQuery = codebaseQueryWords.some(word => userRequest.includes(word)) && (userRequest.includes('what') || userRequest.includes('how many') || userRequest.includes('about')) && - !(hasImages && (userRequest.includes('image') || userRequest.includes('this') || userRequest.includes('that'))) + !(hasImages && (userRequest.includes('image') || userRequest.includes('this') || userRequest.includes('that'))); // Treat web search queries as requiring tools (need to search the web to answer) const isWebQuery = webQueryWords.some(word => userRequest.includes(word)) || (userRequest.includes('search for') && (userRequest.includes('on the web') || userRequest.includes('on the internet'))) || (userRequest.includes('tell me what you know about') || userRequest.includes('what do you know about')) || ((userRequest.includes('what is') || userRequest.includes('who is') || userRequest.includes('when did')) && - (userRequest.includes('latest') || userRequest.includes('current') || userRequest.includes('recent') || userRequest.includes('2024') || userRequest.includes('2025'))) + (userRequest.includes('latest') || userRequest.includes('current') || userRequest.includes('recent') || userRequest.includes('2024') || userRequest.includes('2025'))); const shouldUseTools = (isActionRequest || isCodebaseQuery || isWebQuery) && !info.fullText.toLowerCase().includes('') && @@ -3176,80 +3198,80 @@ Output ONLY the JSON, no other text. Start with { and end with }.` !info.fullText.toLowerCase().includes('') && !info.fullText.toLowerCase().includes('') && - !info.fullText.toLowerCase().includes('') + !info.fullText.toLowerCase().includes(''); // If model refused to use tools after first attempt, synthesize immediately // Skip the retry loop entirely for stubborn models // BUT: Don't synthesize file search tools if images are present (user likely wants image analysis, not file search) - const isEmptyOrShort = !userRequest || userRequest.trim().length < 20 + const isEmptyOrShort = !userRequest || userRequest.trim().length < 20; const isImageAnalysisQuery = hasImages && ( isEmptyOrShort || userRequest.toLowerCase().includes('image') || userRequest.toLowerCase().includes('what') && (userRequest.toLowerCase().includes('about') || userRequest.toLowerCase().includes('show')) || userRequest.toLowerCase().includes('describe') || userRequest.toLowerCase().includes('analyze') - ) + ); // Skip synthesis if user has images and is asking about them // Also skip if we've already read too many files (prevent infinite loops) if (shouldUseTools && nAttempts >= 1 && !isImageAnalysisQuery && filesReadInQuery < MAX_FILES_READ_PER_QUERY) { - const synthesizedToolCall = this._synthesizeToolCallFromIntent(userRequest, originalUserMessage.displayContent || '') + const synthesizedToolCall = this._synthesizeToolCallFromIntent(userRequest, originalUserMessage.displayContent || ''); // Also skip if synthesized call is search_for_files and images are present if (synthesizedToolCall && !(hasImages && synthesizedToolCall.toolName === 'search_for_files')) { - const { toolName, toolParams } = synthesizedToolCall - const toolId = generateUuid() + const { toolName, toolParams } = synthesizedToolCall; + const toolId = generateUuid(); // Add assistant message explaining we're auto-executing - let actionMessage = 'taking action' + let actionMessage = 'taking action'; if (toolName === 'search_for_files') { - actionMessage = 'finding relevant files' + actionMessage = 'finding relevant files'; } else if (toolName === 'read_file') { - actionMessage = 'reading the file' + actionMessage = 'reading the file'; } else if (toolName === 'web_search') { - actionMessage = 'searching the web' + actionMessage = 'searching the web'; } else if (toolName === 'browse_url') { - actionMessage = 'fetching the web page' + actionMessage = 'fetching the web page'; } this._addMessageToThread(threadId, { role: 'assistant', displayContent: `I'll help you with that. Let me start by ${actionMessage}...`, reasoning: '', anthropicReasoning: null - }) - toolSynthesizedAndMessageAdded = true + }); + toolSynthesizedAndMessageAdded = true; // CRITICAL: Check for pending plan before executing synthesized tool // Use fast check if (checkPlanGenerated()) { - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } // Execute the synthesized tool - const mcpTools = this._mcpService.getMCPTools() - const mcpTool = mcpTools?.find(t => t.name === toolName as ToolName) + const mcpTools = this._mcpService.getMCPTools(); + const mcpTool = mcpTools?.find(t => t.name === toolName as ToolName); const { awaitingUserApproval, interrupted } = await this._runToolCall( threadId, toolName as ToolName, toolId, mcpTool?.mcpServerName, { preapproved: false, unvalidatedToolParams: toolParams } - ) + ); if (interrupted) { - this._setStreamState(threadId, undefined) - return + this._setStreamState(threadId, undefined); + return; } if (awaitingUserApproval) { - isRunningWhenEnd = 'awaiting_user' + isRunningWhenEnd = 'awaiting_user'; } else { - shouldSendAnotherMessage = true + shouldSendAnotherMessage = true; } - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); // Skip adding the failed assistant message and break out of retry loop // Tool result is already in thread via _runToolCall, so we'll send another message - break // Exit inner retry loop, continue outer loop with tool results + break; // Exit inner retry loop, continue outer loop with tool results } } } @@ -3259,26 +3281,26 @@ Output ONLY the JSON, no other text. Start with { and end with }.` // Check if message was already added to avoid duplication // Skip if we synthesized a tool and added a message (to prevent duplicate responses) if (!toolSynthesizedAndMessageAdded) { - const thread = this.state.allThreads[threadId] - const lastMessage = thread?.messages[thread.messages.length - 1] + const thread = this.state.allThreads[threadId]; + const lastMessage = thread?.messages[thread.messages.length - 1]; const messageAlreadyAdded = lastMessage?.role === 'assistant' && - lastMessage.displayContent === info.fullText + lastMessage.displayContent === info.fullText; if (!messageAlreadyAdded) { - this._addMessageToThread(threadId, { role: 'assistant', displayContent: info.fullText, reasoning: info.fullReasoning, anthropicReasoning: info.anthropicReasoning }) + this._addMessageToThread(threadId, { role: 'assistant', displayContent: info.fullText, reasoning: info.fullReasoning, anthropicReasoning: info.anthropicReasoning }); } } // PERFORMANCE: Clear stream state immediately to stop showing "running" status // This prevents the UI from continuing to show streaming state after completion - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); // call tool if there is one if (toolCall) { // CRITICAL: Prevent excessive file reads that can cause infinite loops // For codebase queries, limit the number of files read if (toolCall.name === 'read_file') { - filesReadInQuery++ + filesReadInQuery++; if (filesReadInQuery > MAX_FILES_READ_PER_QUERY) { // Too many files read - likely stuck in a loop this._addMessageToThread(threadId, { @@ -3286,101 +3308,101 @@ Output ONLY the JSON, no other text. Start with { and end with }.` displayContent: `I've read ${filesReadInQuery} files, which exceeds the limit. I'll provide an answer based on what I've gathered so far.`, reasoning: '', anthropicReasoning: null - }) - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + }); + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } } // CRITICAL: Check for pending plan before executing tool (fast check) if (checkPlanGenerated()) { - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } // PERFORMANCE: Use cached step from activePlanTracking, don't lookup every time if (activePlanTracking?.currentStep) { - this._linkToolCallToStepInternal(threadId, toolCall.id, activePlanTracking.currentStep) + this._linkToolCallToStepInternal(threadId, toolCall.id, activePlanTracking.currentStep); } - const mcpTools = this._mcpService.getMCPTools() - const mcpTool = mcpTools?.find(t => t.name === toolCall.name) + const mcpTools = this._mcpService.getMCPTools(); + const mcpTool = mcpTools?.find(t => t.name === toolCall.name); - const { awaitingUserApproval, interrupted } = await this._runToolCall(threadId, toolCall.name, toolCall.id, mcpTool?.mcpServerName, { preapproved: false, unvalidatedToolParams: toolCall.rawParams }) + const { awaitingUserApproval, interrupted } = await this._runToolCall(threadId, toolCall.name, toolCall.id, mcpTool?.mcpServerName, { preapproved: false, unvalidatedToolParams: toolCall.rawParams }); if (interrupted) { - this._setStreamState(threadId, undefined) + this._setStreamState(threadId, undefined); if (activePlanTracking?.currentStep) { - this._markStepCompletedInternal(threadId, activePlanTracking.currentStep, false, 'Interrupted by user') - refreshPlanStep() + this._markStepCompletedInternal(threadId, activePlanTracking.currentStep, false, 'Interrupted by user'); + refreshPlanStep(); } - return + return; } // Only update plan step status if we have an active plan (skip if no plan) if (activePlanTracking?.currentStep) { - const thread = this.state.allThreads[threadId] + const thread = this.state.allThreads[threadId]; if (thread) { - const lastMsg = thread.messages[thread.messages.length - 1] + const lastMsg = thread.messages[thread.messages.length - 1]; if (lastMsg && lastMsg.role === 'tool') { - const toolMsg = lastMsg as ToolMessage + const toolMsg = lastMsg as ToolMessage; if (toolMsg.type === 'tool_error') { - this._markStepCompletedInternal(threadId, activePlanTracking.currentStep, false, toolMsg.result || 'Tool execution failed') - refreshPlanStep() + this._markStepCompletedInternal(threadId, activePlanTracking.currentStep, false, toolMsg.result || 'Tool execution failed'); + refreshPlanStep(); } else if (toolMsg.type === 'success') { - this._markStepCompletedInternal(threadId, activePlanTracking.currentStep, true) - refreshPlanStep() + this._markStepCompletedInternal(threadId, activePlanTracking.currentStep, true); + refreshPlanStep(); // Start next step if available (check after refresh) if (activePlanTracking.currentStep && activePlanTracking.currentStep.step.status === 'queued') { - this._startNextStep(threadId) - refreshPlanStep() + this._startNextStep(threadId); + refreshPlanStep(); } } } } } - if (awaitingUserApproval) { isRunningWhenEnd = 'awaiting_user' } - else { shouldSendAnotherMessage = true } + if (awaitingUserApproval) { isRunningWhenEnd = 'awaiting_user'; } + else { shouldSendAnotherMessage = true; } - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) // just decorative, for clarity + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); // just decorative, for clarity } } // end while (attempts) } // end while (send message) // if awaiting user approval, keep isRunning true, else end isRunning - this._setStreamState(threadId, { isRunning: isRunningWhenEnd }) + this._setStreamState(threadId, { isRunning: isRunningWhenEnd }); // add checkpoint before the next user message if (!isRunningWhenEnd) { // PERFORMANCE: Only check plan completion if we were tracking a plan if (activePlanTracking) { // CRITICAL: Refresh plan to get latest step states before checking completion - this._planCache.delete(threadId) - const refreshedPlanInfo = this._getCurrentPlan(threadId, true) + this._planCache.delete(threadId); + const refreshedPlanInfo = this._getCurrentPlan(threadId, true); if (refreshedPlanInfo) { const allStepsComplete = refreshedPlanInfo.plan.steps.every(s => s.disabled || s.status === 'succeeded' || s.status === 'failed' || s.status === 'skipped' - ) + ); if (allStepsComplete && refreshedPlanInfo.plan.approvalState === 'executing') { // Mark plan as completed const updatedPlan: PlanMessage = { ...refreshedPlanInfo.plan, approvalState: 'completed' - } - this._editMessageInThread(threadId, refreshedPlanInfo.planIdx, updatedPlan) + }; + this._editMessageInThread(threadId, refreshedPlanInfo.planIdx, updatedPlan); // Invalidate cache after update - this._planCache.delete(threadId) + this._planCache.delete(threadId); // Generate ReviewMessage with summary (use refreshed plan with latest data) - this._generateReviewMessage(threadId, updatedPlan) + this._generateReviewMessage(threadId, updatedPlan); } } } - this._addUserCheckpoint({ threadId }) + this._addUserCheckpoint({ threadId }); } // capture number of messages sent - this._metricsService.capture('Agent Loop Done', { nMessagesSent, chatMode }) + this._metricsService.capture('Agent Loop Done', { nMessagesSent, chatMode }); } @@ -3391,7 +3413,7 @@ Output ONLY the JSON, no other text. Start with { and end with }.` private _addCheckpoint(threadId: string, checkpoint: CheckpointEntry) { const thread = this.state.allThreads[threadId]; - if (!thread) return; + if (!thread) { return; } // Count existing checkpoints in this thread const existingCheckpoints = thread.messages.filter(m => m.role === 'checkpoint'); @@ -3442,7 +3464,7 @@ Output ONLY the JSON, no other text. Start with { and end with }.` private _getTotalCheckpointSizeMB(): number { let totalBytes = 0; for (const thread of Object.values(this.state.allThreads)) { - if (!thread) continue; + if (!thread) { continue; } for (const msg of thread.messages) { if (msg.role === 'checkpoint') { totalBytes += this._estimateCheckpointSize(msg as CheckpointEntry); @@ -3457,7 +3479,7 @@ Output ONLY the JSON, no other text. Start with { and end with }.` const checkpointList: Array<{ threadId: string; index: number; checkpoint: CheckpointEntry; size: number }> = []; for (const [threadId, thread] of Object.entries(this.state.allThreads)) { - if (!thread) continue; + if (!thread) { continue; } for (let i = 0; i < thread.messages.length; i++) { const msg = thread.messages[i]; if (msg.role === 'checkpoint') { @@ -3480,7 +3502,7 @@ Output ONLY the JSON, no other text. Start with { and end with }.` const toEvict = new Map>(); // threadId -> Set for (const item of checkpointList) { - if (freedMB >= neededMB) break; + if (freedMB >= neededMB) { break; } if (!toEvict.has(item.threadId)) { toEvict.set(item.threadId, new Set()); @@ -3493,11 +3515,11 @@ Output ONLY the JSON, no other text. Start with { and end with }.` const newThreads = { ...this.state.allThreads }; for (const [threadId, indices] of toEvict.entries()) { const thread = newThreads[threadId]; - if (!thread) continue; + if (!thread) { continue; } // Remove in reverse order to preserve indices const sortedIndices = Array.from(indices).sort((a, b) => b - a); - let newMessages = [...thread.messages]; + const newMessages = [...thread.messages]; for (const idx of sortedIndices) { newMessages.splice(idx, 1); } @@ -3513,38 +3535,38 @@ Output ONLY the JSON, no other text. Start with { and end with }.` } private _generateReviewMessage(threadId: string, plan: PlanMessage): void { - const thread = this.state.allThreads[threadId] - if (!thread) return + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } - const succeededSteps = plan.steps.filter(s => s.status === 'succeeded') - const failedSteps = plan.steps.filter(s => s.status === 'failed') - const skippedSteps = plan.steps.filter(s => s.status === 'skipped' || s.disabled) - const completed = failedSteps.length === 0 + const succeededSteps = plan.steps.filter(s => s.status === 'succeeded'); + const failedSteps = plan.steps.filter(s => s.status === 'failed'); + const skippedSteps = plan.steps.filter(s => s.status === 'skipped' || s.disabled); + const completed = failedSteps.length === 0; - const executionTime = plan.executionStartTime ? Date.now() - plan.executionStartTime : undefined - const stepsCompleted = succeededSteps.length - const stepsTotal = plan.steps.length + const executionTime = plan.executionStartTime ? Date.now() - plan.executionStartTime : undefined; + const stepsCompleted = succeededSteps.length; + const stepsTotal = plan.steps.length; // Collect files changed from checkpoints - const filesChanged: Array<{ path: string; changeType: 'created' | 'modified' | 'deleted' }> = [] - const fileSet = new Set() + const filesChanged: Array<{ path: string; changeType: 'created' | 'modified' | 'deleted' }> = []; + const fileSet = new Set(); // Check all checkpoints created during plan execution - const planIdx = findLastIdx(thread.messages, (m: ChatMessage) => m.role === 'plan' && (m as PlanMessage).summary === plan.summary) + const planIdx = findLastIdx(thread.messages, (m: ChatMessage) => m.role === 'plan' && (m as PlanMessage).summary === plan.summary); if (planIdx >= 0) { // Find checkpoints after plan message for (let i = planIdx + 1; i < thread.messages.length; i++) { - const msg = thread.messages[i] + const msg = thread.messages[i]; if (msg.role === 'checkpoint') { - const checkpoint = msg as CheckpointEntry + const checkpoint = msg as CheckpointEntry; for (const fsPath in checkpoint.voidFileSnapshotOfURI) { if (!fileSet.has(fsPath)) { - fileSet.add(fsPath) + fileSet.add(fsPath); // For now, mark as modified (could enhance to detect created/deleted by comparing with initial state) filesChanged.push({ path: fsPath, changeType: 'modified' - }) + }); } } } @@ -3552,26 +3574,26 @@ Output ONLY the JSON, no other text. Start with { and end with }.` } // Collect issues from failed steps - const issues: Array<{ severity: 'error' | 'warning' | 'info'; message: string; file?: string }> = [] + const issues: Array<{ severity: 'error' | 'warning' | 'info'; message: string; file?: string }> = []; for (const step of failedSteps) { issues.push({ severity: 'error', message: step.error || `Step ${step.stepNumber} failed: ${step.description}`, file: step.files?.[0] - }) + }); } // Generate summary let summary = completed ? `Successfully completed all ${stepsCompleted} step${stepsCompleted !== 1 ? 's' : ''} of the plan: ${plan.summary}` - : `Completed ${stepsCompleted} of ${stepsTotal} steps. ${failedSteps.length} step${failedSteps.length !== 1 ? 's' : ''} failed.` + : `Completed ${stepsCompleted} of ${stepsTotal} steps. ${failedSteps.length} step${failedSteps.length !== 1 ? 's' : ''} failed.`; if (skippedSteps.length > 0) { - summary += ` ${skippedSteps.length} step${skippedSteps.length !== 1 ? 's were' : ' was'} skipped.` + summary += ` ${skippedSteps.length} step${skippedSteps.length !== 1 ? 's were' : ' was'} skipped.`; } // Find last checkpoint index - const lastCheckpointIdx = findLastIdx(thread.messages, (m: ChatMessage) => m.role === 'checkpoint') + const lastCheckpointIdx = findLastIdx(thread.messages, (m: ChatMessage) => m.role === 'checkpoint'); const reviewMessage: ReviewMessage = { role: 'review', @@ -3594,17 +3616,17 @@ Output ONLY the JSON, no other text. Start with { and end with }.` 'Test the implementation', 'Continue with additional improvements if needed' ] - } + }; - this._addMessageToThread(threadId, reviewMessage) + this._addMessageToThread(threadId, reviewMessage); } private _editMessageInThread(threadId: string, messageIdx: number, newMessage: ChatMessage,) { - const { allThreads } = this.state - const oldThread = allThreads[threadId] - if (!oldThread) return // should never happen + const { allThreads } = this.state; + const oldThread = allThreads[threadId]; + if (!oldThread) { return; } // should never happen // update state and store it const newThreads = { ...allThreads, @@ -3617,257 +3639,256 @@ Output ONLY the JSON, no other text. Start with { and end with }.` ...oldThread.messages.slice(messageIdx + 1, Infinity), ], } - } - this._storeAllThreads(newThreads) - this._setState({ allThreads: newThreads }) // the current thread just changed (it had a message added to it) + }; + this._storeAllThreads(newThreads); + this._setState({ allThreads: newThreads }); // the current thread just changed (it had a message added to it) // Invalidate plan cache when plan messages are edited if (newMessage.role === 'plan') { - this._planCache.delete(threadId) + this._planCache.delete(threadId); } } private _getCheckpointInfo = (checkpointMessage: ChatMessage & { role: 'checkpoint' }, fsPath: string, opts: { includeUserModifiedChanges: boolean }) => { - const voidFileSnapshot = checkpointMessage.voidFileSnapshotOfURI ? checkpointMessage.voidFileSnapshotOfURI[fsPath] ?? null : null - if (!opts.includeUserModifiedChanges) { return { voidFileSnapshot, } } + const voidFileSnapshot = checkpointMessage.voidFileSnapshotOfURI ? checkpointMessage.voidFileSnapshotOfURI[fsPath] ?? null : null; + if (!opts.includeUserModifiedChanges) { return { voidFileSnapshot, }; } - const userModifiedCortexideFileSnapshot = fsPath in checkpointMessage.userModifications.voidFileSnapshotOfURI ? checkpointMessage.userModifications.voidFileSnapshotOfURI[fsPath] ?? null : null - return { voidFileSnapshot: userModifiedCortexideFileSnapshot ?? voidFileSnapshot, } - } + const userModifiedCortexideFileSnapshot = fsPath in checkpointMessage.userModifications.voidFileSnapshotOfURI ? checkpointMessage.userModifications.voidFileSnapshotOfURI[fsPath] ?? null : null; + return { voidFileSnapshot: userModifiedCortexideFileSnapshot ?? voidFileSnapshot, }; + }; private _computeNewCheckpointInfo({ threadId }: { threadId: string }) { - const thread = this.state.allThreads[threadId] - if (!thread) return + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } - const lastCheckpointIdx = findLastIdx(thread.messages, (m) => m.role === 'checkpoint') ?? -1 - if (lastCheckpointIdx === -1) return + const lastCheckpointIdx = findLastIdx(thread.messages, (m) => m.role === 'checkpoint') ?? -1; + if (lastCheckpointIdx === -1) { return; } - const voidFileSnapshotOfURI: { [fsPath: string]: CortexideFileSnapshot | undefined } = {} + const voidFileSnapshotOfURI: { [fsPath: string]: CortexideFileSnapshot | undefined } = {}; // add a change for all the URIs in the checkpoint history - const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: 0, hiIdx: lastCheckpointIdx, }) ?? {} + const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: 0, hiIdx: lastCheckpointIdx, }) ?? {}; for (const fsPath in lastIdxOfURI ?? {}) { - const { model } = this._cortexideModelService.getModelFromFsPath(fsPath) - if (!model) continue - const checkpoint2 = thread.messages[lastIdxOfURI[fsPath]] || null - if (!checkpoint2) continue - if (checkpoint2.role !== 'checkpoint') continue - const res = this._getCheckpointInfo(checkpoint2, fsPath, { includeUserModifiedChanges: false }) - if (!res) continue - const { voidFileSnapshot: oldCortexideFileSnapshot } = res + const { model } = this._cortexideModelService.getModelFromFsPath(fsPath); + if (!model) { continue; } + const checkpoint2 = thread.messages[lastIdxOfURI[fsPath]] || null; + if (!checkpoint2) { continue; } + if (checkpoint2.role !== 'checkpoint') { continue; } + const res = this._getCheckpointInfo(checkpoint2, fsPath, { includeUserModifiedChanges: false }); + if (!res) { continue; } + const { voidFileSnapshot: oldCortexideFileSnapshot } = res; // if there was any change to the str or diffAreaSnapshot, update. rough approximation of equality, oldDiffAreasSnapshot === diffAreasSnapshot is not perfect - const voidFileSnapshot = this._editCodeService.getCortexideFileSnapshot(URI.file(fsPath)) - if (oldCortexideFileSnapshot === voidFileSnapshot) continue - voidFileSnapshotOfURI[fsPath] = voidFileSnapshot + const voidFileSnapshot = this._editCodeService.getCortexideFileSnapshot(URI.file(fsPath)); + if (oldCortexideFileSnapshot === voidFileSnapshot) { continue; } + voidFileSnapshotOfURI[fsPath] = voidFileSnapshot; } - return { voidFileSnapshotOfURI } + return { voidFileSnapshotOfURI }; } private _addUserCheckpoint({ threadId }: { threadId: string }) { - const { voidFileSnapshotOfURI } = this._computeNewCheckpointInfo({ threadId }) ?? {} + const { voidFileSnapshotOfURI } = this._computeNewCheckpointInfo({ threadId }) ?? {}; this._addCheckpoint(threadId, { role: 'checkpoint', type: 'user_edit', voidFileSnapshotOfURI: voidFileSnapshotOfURI ?? {}, userModifications: { voidFileSnapshotOfURI: {}, }, - }) + }); } // call this right after LLM edits a file - private _addToolEditCheckpoint({ threadId, uri, }: { threadId: string, uri: URI }) { - const thread = this.state.allThreads[threadId] - if (!thread) return - const { model } = this._cortexideModelService.getModel(uri) - if (!model) return // should never happen - const diffAreasSnapshot = this._editCodeService.getCortexideFileSnapshot(uri) + private _addToolEditCheckpoint({ threadId, uri, }: { threadId: string; uri: URI }) { + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } + const { model } = this._cortexideModelService.getModel(uri); + if (!model) { return; } // should never happen + const diffAreasSnapshot = this._editCodeService.getCortexideFileSnapshot(uri); this._addCheckpoint(threadId, { role: 'checkpoint', type: 'tool_edit', voidFileSnapshotOfURI: { [uri.fsPath]: diffAreasSnapshot }, userModifications: { voidFileSnapshotOfURI: {} }, - }) + }); } - private _getCheckpointBeforeMessage = ({ threadId, messageIdx }: { threadId: string, messageIdx: number }): [CheckpointEntry, number] | undefined => { - const thread = this.state.allThreads[threadId] - if (!thread) return undefined + private _getCheckpointBeforeMessage = ({ threadId, messageIdx }: { threadId: string; messageIdx: number }): [CheckpointEntry, number] | undefined => { + const thread = this.state.allThreads[threadId]; + if (!thread) { return undefined; } for (let i = messageIdx; i >= 0; i--) { - const message = thread.messages[i] + const message = thread.messages[i]; if (message.role === 'checkpoint') { - return [message, i] + return [message, i]; } } - return undefined - } + return undefined; + }; - private _getCheckpointsBetween({ threadId, loIdx, hiIdx }: { threadId: string, loIdx: number, hiIdx: number }) { - const thread = this.state.allThreads[threadId] - if (!thread) return { lastIdxOfURI: {} } // should never happen - const lastIdxOfURI: { [fsPath: string]: number } = {} + private _getCheckpointsBetween({ threadId, loIdx, hiIdx }: { threadId: string; loIdx: number; hiIdx: number }) { + const thread = this.state.allThreads[threadId]; + if (!thread) { return { lastIdxOfURI: {} }; } // should never happen + const lastIdxOfURI: { [fsPath: string]: number } = {}; for (let i = loIdx; i <= hiIdx; i += 1) { - const message = thread.messages[i] - if (message?.role !== 'checkpoint') continue + const message = thread.messages[i]; + if (message?.role !== 'checkpoint') { continue; } for (const fsPath in message.voidFileSnapshotOfURI) { // do not include userModified.beforeStrOfURI here, jumping should not include those changes - lastIdxOfURI[fsPath] = i + lastIdxOfURI[fsPath] = i; } } - return { lastIdxOfURI } + return { lastIdxOfURI }; } private _readCurrentCheckpoint(threadId: string): [CheckpointEntry, number] | undefined { - const thread = this.state.allThreads[threadId] - if (!thread) return + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } - const { currCheckpointIdx } = thread.state - if (currCheckpointIdx === null) return + const { currCheckpointIdx } = thread.state; + if (currCheckpointIdx === null) { return; } - const checkpoint = thread.messages[currCheckpointIdx] - if (!checkpoint) return - if (checkpoint.role !== 'checkpoint') return - return [checkpoint, currCheckpointIdx] + const checkpoint = thread.messages[currCheckpointIdx]; + if (!checkpoint) { return; } + if (checkpoint.role !== 'checkpoint') { return; } + return [checkpoint, currCheckpointIdx]; } private _addUserModificationsToCurrCheckpoint({ threadId }: { threadId: string }) { - const { voidFileSnapshotOfURI } = this._computeNewCheckpointInfo({ threadId }) ?? {} - const res = this._readCurrentCheckpoint(threadId) - if (!res) return - const [checkpoint, checkpointIdx] = res + const { voidFileSnapshotOfURI } = this._computeNewCheckpointInfo({ threadId }) ?? {}; + const res = this._readCurrentCheckpoint(threadId); + if (!res) { return; } + const [checkpoint, checkpointIdx] = res; this._editMessageInThread(threadId, checkpointIdx, { ...checkpoint, userModifications: { voidFileSnapshotOfURI: voidFileSnapshotOfURI ?? {}, }, - }) + }); } private _makeUsStandOnCheckpoint({ threadId }: { threadId: string }) { - const thread = this.state.allThreads[threadId] - if (!thread) return + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } if (thread.state.currCheckpointIdx === null) { - const lastMsg = thread.messages[thread.messages.length - 1] - if (lastMsg?.role !== 'checkpoint') - this._addUserCheckpoint({ threadId }) - this._setThreadState(threadId, { currCheckpointIdx: thread.messages.length - 1 }) + const lastMsg = thread.messages[thread.messages.length - 1]; + if (lastMsg?.role !== 'checkpoint') { this._addUserCheckpoint({ threadId }); } + this._setThreadState(threadId, { currCheckpointIdx: thread.messages.length - 1 }); } } - jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified }: { threadId: string, messageIdx: number, jumpToUserModified: boolean }) { + jumpToCheckpointBeforeMessageIdx({ threadId, messageIdx, jumpToUserModified }: { threadId: string; messageIdx: number; jumpToUserModified: boolean }) { // if null, add a new temp checkpoint so user can jump forward again - this._makeUsStandOnCheckpoint({ threadId }) + this._makeUsStandOnCheckpoint({ threadId }); - const thread = this.state.allThreads[threadId] - if (!thread) return - if (this.streamState[threadId]?.isRunning) return + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } + if (this.streamState[threadId]?.isRunning) { return; } - const c = this._getCheckpointBeforeMessage({ threadId, messageIdx }) - if (c === undefined) return // should never happen + const c = this._getCheckpointBeforeMessage({ threadId, messageIdx }); + if (c === undefined) { return; } // should never happen - const fromIdx = thread.state.currCheckpointIdx - if (fromIdx === null) return // should never happen + const fromIdx = thread.state.currCheckpointIdx; + if (fromIdx === null) { return; } // should never happen - const [_, toIdx] = c - if (toIdx === fromIdx) return + const [_, toIdx] = c; + if (toIdx === fromIdx) { return; } // console.log(`going from ${fromIdx} to ${toIdx}`) // update the user's checkpoint - this._addUserModificationsToCurrCheckpoint({ threadId }) + this._addUserModificationsToCurrCheckpoint({ threadId }); /* -if undoing - -A,B,C are all files. -x means a checkpoint where the file changed. - -A B C D E F G H I - x x x x x x <-- you can't always go up to find the "before" version; sometimes you need to go down - | | | | | | x ---x-|-|-|-x---x-|----- <-- to - | | | | x x - | | x x | - | | | | -----x-|---x-x------- <-- from - x - -We need to revert anything that happened between to+1 and from. -**We do this by finding the last x from 0...`to` for each file and applying those contents.** -We only need to do it for files that were edited since `to`, ie files between to+1...from. + * if undoing + * + * A,B,C are all files. + * x means a checkpoint where the file changed. + * + * A B C D E F G H I + * x x x x x x <-- you can't always go up to find the "before" version; sometimes you need to go down + * | | | | | | x + * --x-|-|-|-x---x-|----- <-- to + * | | | | x x + * | | x x | + * | | | | + * ----x-|---x-x------- <-- from + * x + * + * We need to revert anything that happened between to+1 and from. + * **We do this by finding the last x from 0...`to` for each file and applying those contents.** + * We only need to do it for files that were edited since `to`, ie files between to+1...from. */ if (toIdx < fromIdx) { - const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: toIdx + 1, hiIdx: fromIdx }) + const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: toIdx + 1, hiIdx: fromIdx }); const idxes = function* () { for (let k = toIdx; k >= 0; k -= 1) { // first go up - yield k + yield k; } for (let k = toIdx + 1; k < thread.messages.length; k += 1) { // then go down - yield k + yield k; } - } + }; for (const fsPath in lastIdxOfURI) { // find the first instance of this file starting at toIdx (go up to latest file; if there is none, go down) for (const k of idxes()) { - const message = thread.messages[k] - if (message.role !== 'checkpoint') continue - const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified }) - if (!res) continue - const { voidFileSnapshot } = res - if (!voidFileSnapshot) continue - this._editCodeService.restoreCortexideFileSnapshot(URI.file(fsPath), voidFileSnapshot) - break + const message = thread.messages[k]; + if (message.role !== 'checkpoint') { continue; } + const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified }); + if (!res) { continue; } + const { voidFileSnapshot } = res; + if (!voidFileSnapshot) { continue; } + this._editCodeService.restoreCortexideFileSnapshot(URI.file(fsPath), voidFileSnapshot); + break; } } } /* -if redoing - -A B C D E F G H I J - x x x x x x x - | | | | | | x x x ---x-|-|-|-x---x-|-|--- <-- from - | | | | x x - | | x x | - | | | | -----x-|---x-x-----|--- <-- to - x x - - -We need to apply latest change for anything that happened between from+1 and to. -We only need to do it for files that were edited since `from`, ie files between from+1...to. + * if redoing + * + * A B C D E F G H I J + * x x x x x x x + * | | | | | | x x x + * --x-|-|-|-x---x-|-|--- <-- from + * | | | | x x + * | | x x | + * | | | | + * ----x-|---x-x-----|--- <-- to + * x x + * + * + * We need to apply latest change for anything that happened between from+1 and to. + * We only need to do it for files that were edited since `from`, ie files between from+1...to. */ if (toIdx > fromIdx) { - const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: fromIdx + 1, hiIdx: toIdx }) + const { lastIdxOfURI } = this._getCheckpointsBetween({ threadId, loIdx: fromIdx + 1, hiIdx: toIdx }); for (const fsPath in lastIdxOfURI) { // apply lowest down content for each uri for (let k = toIdx; k >= fromIdx + 1; k -= 1) { - const message = thread.messages[k] - if (message.role !== 'checkpoint') continue - const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified }) - if (!res) continue - const { voidFileSnapshot } = res - if (!voidFileSnapshot) continue - this._editCodeService.restoreCortexideFileSnapshot(URI.file(fsPath), voidFileSnapshot) - break + const message = thread.messages[k]; + if (message.role !== 'checkpoint') { continue; } + const res = this._getCheckpointInfo(message, fsPath, { includeUserModifiedChanges: jumpToUserModified }); + if (!res) { continue; } + const { voidFileSnapshot } = res; + if (!voidFileSnapshot) { continue; } + this._editCodeService.restoreCortexideFileSnapshot(URI.file(fsPath), voidFileSnapshot); + break; } } } - this._setThreadState(threadId, { currCheckpointIdx: toIdx }) + this._setThreadState(threadId, { currCheckpointIdx: toIdx }); } private _wrapRunAgentToNotify(p: Promise, threadId: string) { const notify = ({ error }: { error: string | null }) => { - const thread = this.state.allThreads[threadId] - if (!thread) return - const userMsg = findLast(thread.messages, m => m.role === 'user') - if (!userMsg) return - if (userMsg.role !== 'user') return - const messageContent = truncate(userMsg.displayContent, 50, '...') + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } + const userMsg = findLast(thread.messages, m => m.role === 'user'); + if (!userMsg) { return; } + if (userMsg.role !== 'user') { return; } + const messageContent = truncate(userMsg.displayContent, 50, '...'); this._notificationService.notify({ severity: error ? Severity.Warning : Severity.Info, @@ -3882,55 +3903,55 @@ We only need to do it for files that were edited since `from`, ie files between tooltip: '', class: undefined, run: () => { - this.switchToThread(threadId) + this.switchToThread(threadId); // scroll to bottom this.state.allThreads[threadId]?.state.mountedInfo?.whenMounted.then(m => { - m.scrollToBottom() - }) + m.scrollToBottom(); + }); } }] }, - }) - } + }); + }; p.then(() => { - if (threadId !== this.state.currentThreadId) notify({ error: null }) + if (threadId !== this.state.currentThreadId) { notify({ error: null }); } }).catch((e) => { - if (threadId !== this.state.currentThreadId) notify({ error: getErrorMessage(e) }) - throw e - }) + if (threadId !== this.state.currentThreadId) { notify({ error: getErrorMessage(e) }); } + throw e; + }); } dismissStreamError(threadId: string): void { - this._setStreamState(threadId, undefined) + this._setStreamState(threadId, undefined); } - private async _addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId, images, pdfs, noPlan, displayContent }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string, images?: ChatImageAttachment[], pdfs?: ChatPDFAttachment[], noPlan?: boolean, displayContent?: string }) { - const thread = this.state.allThreads[threadId] - if (!thread) return // should never happen + private async _addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId, images, pdfs, noPlan, displayContent }: { userMessage: string; _chatSelections?: StagingSelectionItem[]; threadId: string; images?: ChatImageAttachment[]; pdfs?: ChatPDFAttachment[]; noPlan?: boolean; displayContent?: string }) { + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } // should never happen // interrupt existing stream if (this.streamState[threadId]?.isRunning) { - await this.abortRunning(threadId) + await this.abortRunning(threadId); } // add dummy before this message to keep checkpoint before user message idea consistent if (thread.messages.length === 0) { - this._addUserCheckpoint({ threadId }) + this._addUserCheckpoint({ threadId }); } // Optionally suppress plan generation for this message if (noPlan) { - this._suppressPlanOnceByThread[threadId] = true + this._suppressPlanOnceByThread[threadId] = true; } // add user's message to chat history - const instructions = userMessage - const currSelns: StagingSelectionItem[] = _chatSelections ?? thread.state.stagingSelections + const instructions = userMessage; + const currSelns: StagingSelectionItem[] = _chatSelections ?? thread.state.stagingSelections; - let userMessageContent = await chat_userMessageContent(instructions, currSelns, { directoryStrService: this._directoryStringService, fileService: this._fileService }) // user message + names of files (NOT content) + let userMessageContent = await chat_userMessageContent(instructions, currSelns, { directoryStrService: this._directoryStringService, fileService: this._fileService }); // user message + names of files (NOT content) // Append PDF extracted text to message content for context if (pdfs && pdfs.length > 0) { @@ -3966,14 +3987,14 @@ We only need to do it for files that were edited since `from`, ie files between } } - const userHistoryElt: ChatMessage = { role: 'user', content: userMessageContent, displayContent: displayContent || instructions, selections: currSelns, images, pdfs, state: defaultMessageState } - this._addMessageToThread(threadId, userHistoryElt) + const userHistoryElt: ChatMessage = { role: 'user', content: userMessageContent, displayContent: displayContent || instructions, selections: currSelns, images, pdfs, state: defaultMessageState }; + this._addMessageToThread(threadId, userHistoryElt); - this._setThreadState(threadId, { currCheckpointIdx: null }) // no longer at a checkpoint because started streaming + this._setThreadState(threadId, { currCheckpointIdx: null }); // no longer at a checkpoint because started streaming // Set early preparing state to give immediate feedback - let preparationCancelled = false - const preparationInterruptor = Promise.resolve(() => { preparationCancelled = true }) + let preparationCancelled = false; + const preparationInterruptor = Promise.resolve(() => { preparationCancelled = true; }); this._setStreamState(threadId, { isRunning: 'preparing', llmInfo: { @@ -3982,20 +4003,20 @@ We only need to do it for files that were edited since `from`, ie files between toolCallSoFar: null }, interrupt: preparationInterruptor - }) + }); // Check if user selected "Auto" mode - const userModelSelection = this._currentModelSelectionProps().modelSelection - const isAutoMode = userModelSelection?.providerName === 'auto' && userModelSelection?.modelName === 'auto' + const userModelSelection = this._currentModelSelectionProps().modelSelection; + const isAutoMode = userModelSelection?.providerName === 'auto' && userModelSelection?.modelName === 'auto'; // Auto-select model based on task context if in auto mode, otherwise use user's selection // Generate requestId early for router tracking in auto mode, then reuse it in _runChatAgent - const earlyRequestId = isAutoMode ? generateUuid() : undefined - let modelSelection: ModelSelection | null + const earlyRequestId = isAutoMode ? generateUuid() : undefined; + let modelSelection: ModelSelection | null; // PERFORMANCE: Start prompt prep in parallel with router decision for auto mode // This can save 50-200ms by doing work that doesn't need model selection - let repoIndexerPromise: Promise<{ results: string[], metrics: any } | null> | undefined + let repoIndexerPromise: Promise<{ results: string[]; metrics: unknown } | null> | undefined; if (isAutoMode && earlyRequestId) { // Update status to show model selection in progress if (!preparationCancelled) { @@ -4007,52 +4028,61 @@ We only need to do it for files that were edited since `from`, ie files between toolCallSoFar: null }, interrupt: preparationInterruptor - }) + }); } // Track router timing for auto mode - chatLatencyAudit.startRequest(earlyRequestId, 'auto', 'auto') - chatLatencyAudit.markRouterStart(earlyRequestId) + chatLatencyAudit.startRequest(earlyRequestId, 'auto', 'auto'); + chatLatencyAudit.markRouterStart(earlyRequestId); // Start router decision and repo indexer query in parallel // PERFORMANCE: Repo indexer query doesn't need model selection - start it early - const routerPromise = this._autoSelectModel(instructions, images, pdfs) - const thread = this.state.allThreads[threadId] - const chatMessages = thread?.messages ?? [] - const { chatMode } = this._settingsService.state.globalSettings + const routerPromise = this._autoSelectModel(instructions, images, pdfs); + const thread = this.state.allThreads[threadId]; + const chatMessages = thread?.messages ?? []; + const { chatMode } = this._settingsService.state.globalSettings; // Start repo indexer query in parallel (saves 50-200ms) - repoIndexerPromise = this._convertToLLMMessagesService.startRepoIndexerQuery(chatMessages, chatMode) + repoIndexerPromise = this._convertToLLMMessagesService.startRepoIndexerQuery(chatMessages, chatMode); // Wait for router decision - const autoSelectedModel = await routerPromise - chatLatencyAudit.markRouterEnd(earlyRequestId) - modelSelection = autoSelectedModel + const autoSelectedModel = await routerPromise; + chatLatencyAudit.markRouterEnd(earlyRequestId); + modelSelection = autoSelectedModel; // CRITICAL: If auto selection failed, we need a fallback to prevent null modelSelection // This ensures we never send empty messages to the API (which causes "invalid message format" error) - if (!modelSelection) { + // Check for both null and the invalid "auto" placeholder (which indicates routing failed) + if (!modelSelection || isAutoModelSelection(modelSelection)) { // Try to get any available model as fallback - const fallbackModel = this._getFallbackModel() + const fallbackModel = this._getFallbackModel(); if (fallbackModel) { - modelSelection = fallbackModel - this._notificationService.warn('Auto model selection failed. Using fallback model. Please configure your model providers.') + modelSelection = fallbackModel; + this._notificationService.warn('Auto model selection failed. Using fallback model. Please configure your model providers.'); } else { // Last resort: show error and don't proceed - this._notificationService.error('No models available. Please configure at least one model provider in settings.') - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + this._notificationService.error('No models available. Please configure at least one model provider in settings.'); + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; } } } else { - modelSelection = userModelSelection + modelSelection = userModelSelection; } - // Final validation: ensure modelSelection is not null before proceeding - if (!modelSelection) { - this._notificationService.error('No model selected. Please select a model in settings.') - this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }) - return + // Final validation: ensure modelSelection is not null or "auto" before proceeding + // This is a critical safety check that catches any edge cases + if (!modelSelection || isAutoModelSelection(modelSelection)) { + // Try fallback one more time (defensive programming) + const fallbackModel = this._getFallbackModel(); + if (fallbackModel) { + modelSelection = fallbackModel; + this._notificationService.warn('Invalid model selection detected. Using fallback model.'); + } else { + this._notificationService.error('No models available. Please configure at least one model provider in settings.'); + this._setStreamState(threadId, { isRunning: 'idle', interrupt: 'not_needed' }); + return; + } } // Validate model capabilities if attachments are present @@ -4085,8 +4115,8 @@ We only need to do it for files that were edited since `from`, ie files between // Check if preparation was cancelled if (preparationCancelled) { - this._setStreamState(threadId, undefined) - return + this._setStreamState(threadId, undefined); + return; } // Update status to show request preparation @@ -4098,12 +4128,12 @@ We only need to do it for files that were edited since `from`, ie files between toolCallSoFar: null }, interrupt: preparationInterruptor - }) + }); // Get model options (skip for "auto" since it's not a real model) const modelSelectionOptions = modelSelection && !(modelSelection.providerName === 'auto' && modelSelection.modelName === 'auto') ? this._settingsService.state.optionsOfModelSelection['Chat'][modelSelection.providerName]?.[modelSelection.modelName] - : undefined + : undefined; // repoIndexerPromise is already set above if in auto mode @@ -4111,18 +4141,18 @@ We only need to do it for files that were edited since `from`, ie files between this._wrapRunAgentToNotify( this._runChatAgent({ threadId, modelSelection, modelSelectionOptions, earlyRequestId, isAutoMode, repoIndexerPromise }), threadId, - ) + ); // scroll to bottom this.state.allThreads[threadId]?.state.mountedInfo?.whenMounted.then(m => { - m.scrollToBottom() - }) + m.scrollToBottom(); + }); } - async addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId, images, pdfs, noPlan, displayContent }: { userMessage: string, _chatSelections?: StagingSelectionItem[], threadId: string, images?: ChatImageAttachment[], pdfs?: ChatPDFAttachment[], noPlan?: boolean, displayContent?: string }) { + async addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId, images, pdfs, noPlan, displayContent }: { userMessage: string; _chatSelections?: StagingSelectionItem[]; threadId: string; images?: ChatImageAttachment[]; pdfs?: ChatPDFAttachment[]; noPlan?: boolean; displayContent?: string }) { const thread = this.state.allThreads[threadId]; - if (!thread) return + if (!thread) { return; } // if there's a current checkpoint, delete all messages after it if (thread.state.currCheckpointIdx !== null) { @@ -4147,29 +4177,29 @@ We only need to do it for files that were edited since `from`, ie files between await this._addUserMessageAndStreamResponse({ userMessage, _chatSelections, threadId, images, pdfs, noPlan, displayContent }); // Safety: ensure stream state is cleared when the stream finishes (unless awaiting user approval) - const s = this.streamState[threadId] + const s = this.streamState[threadId]; if (!s || s.isRunning === undefined || s.isRunning === 'idle' || s.isRunning === 'awaiting_user') { - return + return; } // If still running after completion, clear it (stream should have been handled by _addUserMessageAndStreamResponse) - this._setStreamState(threadId, undefined) + this._setStreamState(threadId, undefined); } editUserMessageAndStreamResponse: IChatThreadService['editUserMessageAndStreamResponse'] = async ({ userMessage, messageIdx, threadId }) => { - const thread = this.state.allThreads[threadId] - if (!thread) return // should never happen + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } // should never happen if (thread.messages?.[messageIdx]?.role !== 'user') { - throw new Error(`Error: editing a message with role !=='user'`) + throw new Error(`Error: editing a message with role !=='user'`); } // get prev and curr selections before clearing the message - const currSelns = thread.messages[messageIdx].state.stagingSelections || [] // staging selections for the edited message + const currSelns = thread.messages[messageIdx].state.stagingSelections || []; // staging selections for the edited message // clear messages up to the index - const slicedMessages = thread.messages.slice(0, messageIdx) + const slicedMessages = thread.messages.slice(0, messageIdx); this._setState({ allThreads: { ...this.state.allThreads, @@ -4178,55 +4208,55 @@ We only need to do it for files that were edited since `from`, ie files between messages: slicedMessages } } - }) + }); // re-add the message and stream it - this._addUserMessageAndStreamResponse({ userMessage, _chatSelections: currSelns, threadId }) - } + this._addUserMessageAndStreamResponse({ userMessage, _chatSelections: currSelns, threadId }); + }; // ---------- the rest ---------- private _getAllSeenFileURIs(threadId: string) { - const thread = this.state.allThreads[threadId] - if (!thread) return [] + const thread = this.state.allThreads[threadId]; + if (!thread) { return []; } - const fsPathsSet = new Set() - const uris: URI[] = [] + const fsPathsSet = new Set(); + const uris: URI[] = []; const addURI = (uri: URI) => { - if (!fsPathsSet.has(uri.fsPath)) uris.push(uri) - fsPathsSet.add(uri.fsPath) - uris.push(uri) - } + if (!fsPathsSet.has(uri.fsPath)) { uris.push(uri); } + fsPathsSet.add(uri.fsPath); + uris.push(uri); + }; for (const m of thread.messages) { // URIs of user selections if (m.role === 'user') { for (const sel of m.selections ?? []) { - addURI(sel.uri) + addURI(sel.uri); } } // URIs of files that have been read else if (m.role === 'tool' && m.type === 'success' && m.name === 'read_file') { - const params = m.params as BuiltinToolCallParams['read_file'] - addURI(params.uri) + const params = m.params as BuiltinToolCallParams['read_file']; + addURI(params.uri); } } - return uris + return uris; } getRelativeStr = (uri: URI) => { - const isInside = this._workspaceContextService.isInsideWorkspace(uri) + const isInside = this._workspaceContextService.isInsideWorkspace(uri); if (isInside) { - const f = this._workspaceContextService.getWorkspace().folders.find(f => uri.fsPath.startsWith(f.uri.fsPath)) - if (f) { return uri.fsPath.replace(f.uri.fsPath, '') } - else { return undefined } + const f = this._workspaceContextService.getWorkspace().folders.find(f => uri.fsPath.startsWith(f.uri.fsPath)); + if (f) { return uri.fsPath.replace(f.uri.fsPath, ''); } + else { return undefined; } } else { - return undefined + return undefined; } - } + }; // gets the location of codespan link so the user can click on it @@ -4237,37 +4267,37 @@ We only need to do it for files that were edited since `from`, ie files between const functionOrMethodPattern = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; // `fUnCt10n_name` const functionParensPattern = /^([^\s(]+)\([^)]*\)$/; // `functionName( args )` - let target = _codespanStr // the string to search for - let codespanType: 'file-or-folder' | 'function-or-class' + let target = _codespanStr; // the string to search for + let codespanType: 'file-or-folder' | 'function-or-class'; if (target.includes('.') || target.includes('/')) { - codespanType = 'file-or-folder' - target = _codespanStr + codespanType = 'file-or-folder'; + target = _codespanStr; } else if (functionOrMethodPattern.test(target)) { - codespanType = 'function-or-class' - target = _codespanStr + codespanType = 'function-or-class'; + target = _codespanStr; } else if (functionParensPattern.test(target)) { - const match = target.match(functionParensPattern) + const match = target.match(functionParensPattern); if (match && match[1]) { - codespanType = 'function-or-class' - target = match[1] + codespanType = 'function-or-class'; + target = match[1]; } - else { return null } + else { return null; } } else { - return null + return null; } // get history of all AI and user added files in conversation + store in reverse order (MRU) - const prevUris = this._getAllSeenFileURIs(threadId).reverse() + const prevUris = this._getAllSeenFileURIs(threadId).reverse(); if (codespanType === 'file-or-folder') { - const doesUriMatchTarget = (uri: URI) => uri.path.includes(target) + const doesUriMatchTarget = (uri: URI) => uri.path.includes(target); // check if any prevFiles are the `target` for (const [idx, uri] of prevUris.entries()) { @@ -4276,42 +4306,42 @@ We only need to do it for files that were edited since `from`, ie files between // shorten it // TODO make this logic more general - const prevUriStrs = prevUris.map(uri => uri.fsPath) - const shortenedUriStrs = shorten(prevUriStrs) - let displayText = shortenedUriStrs[idx] + const prevUriStrs = prevUris.map(uri => uri.fsPath); + const shortenedUriStrs = shorten(prevUriStrs); + let displayText = shortenedUriStrs[idx]; const ellipsisIdx = displayText.lastIndexOf('…/'); if (ellipsisIdx >= 0) { - displayText = displayText.slice(ellipsisIdx + 2) + displayText = displayText.slice(ellipsisIdx + 2); } - return { uri, displayText } + return { uri, displayText }; } } // else search codebase for `target` - let uris: URI[] = [] + let uris: URI[] = []; try { - const { result } = await this._toolsService.callTool['search_pathnames_only']({ query: target, includePattern: null, pageNumber: 0 }) - const { uris: uris_ } = await result - uris = uris_ + const { result } = await this._toolsService.callTool['search_pathnames_only']({ query: target, includePattern: null, pageNumber: 0 }); + const { uris: uris_ } = await result; + uris = uris_; } catch (e) { - return null + return null; } for (const [idx, uri] of uris.entries()) { if (doesUriMatchTarget(uri)) { // TODO make this logic more general - const prevUriStrs = prevUris.map(uri => uri.fsPath) - const shortenedUriStrs = shorten(prevUriStrs) - let displayText = shortenedUriStrs[idx] + const prevUriStrs = prevUris.map(uri => uri.fsPath); + const shortenedUriStrs = shorten(prevUriStrs); + let displayText = shortenedUriStrs[idx]; const ellipsisIdx = displayText.lastIndexOf('…/'); if (ellipsisIdx >= 0) { - displayText = displayText.slice(ellipsisIdx + 2) + displayText = displayText.slice(ellipsisIdx + 2); } - return { uri, displayText } + return { uri, displayText }; } } @@ -4324,9 +4354,9 @@ We only need to do it for files that were edited since `from`, ie files between // check all prevUris for the target for (const uri of prevUris) { - const modelRef = await this._cortexideModelService.getModelSafe(uri) - const { model } = modelRef - if (!model) continue + const modelRef = await this._cortexideModelService.getModelSafe(uri); + const { model } = modelRef; + if (!model) { continue; } const matches = model.findMatches( target, @@ -4348,7 +4378,7 @@ We only need to do it for files that were edited since `from`, ie files between const _definitions = await provider.provideDefinition(model, position, CancellationToken.None); - if (!_definitions) continue; + if (!_definitions) { continue; } const definitions = Array.isArray(_definitions) ? _definitions : [_definitions]; @@ -4373,25 +4403,25 @@ We only need to do it for files that were edited since `from`, ie files between } - return null + return null; - } + }; - getCodespanLink({ codespanStr, messageIdx, threadId }: { codespanStr: string, messageIdx: number, threadId: string }): CodespanLocationLink | undefined { - const thread = this.state.allThreads[threadId] - if (!thread) return undefined; + getCodespanLink({ codespanStr, messageIdx, threadId }: { codespanStr: string; messageIdx: number; threadId: string }): CodespanLocationLink | undefined { + const thread = this.state.allThreads[threadId]; + if (!thread) { return undefined; } - const links = thread.state.linksOfMessageIdx?.[messageIdx] - if (!links) return undefined; + const links = thread.state.linksOfMessageIdx?.[messageIdx]; + if (!links) { return undefined; } - const link = links[codespanStr] + const link = links[codespanStr]; - return link + return link; } - async addCodespanLink({ newLinkText, newLinkLocation, messageIdx, threadId }: { newLinkText: string, newLinkLocation: CodespanLocationLink, messageIdx: number, threadId: string }) { - const thread = this.state.allThreads[threadId] - if (!thread) return + async addCodespanLink({ newLinkText, newLinkLocation, messageIdx, threadId }: { newLinkText: string; newLinkLocation: CodespanLocationLink; messageIdx: number; threadId: string }) { + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } this._setState({ @@ -4412,101 +4442,123 @@ We only need to do it for files that were edited since `from`, ie files between } } - }) + }); } getCurrentThread(): ThreadType { - const state = this.state - const thread = state.allThreads[state.currentThreadId] - if (!thread) throw new Error(`Current thread should never be undefined`) - return thread + const state = this.state; + const thread = state.allThreads[state.currentThreadId]; + if (!thread) { throw new Error(`Current thread should never be undefined`); } + return thread; } getCurrentFocusedMessageIdx() { - const thread = this.getCurrentThread() + const thread = this.getCurrentThread(); // get the focusedMessageIdx - const focusedMessageIdx = thread.state.focusedMessageIdx - if (focusedMessageIdx === undefined) return; + const focusedMessageIdx = thread.state.focusedMessageIdx; + if (focusedMessageIdx === undefined) { return; } // check that the message is actually being edited - const focusedMessage = thread.messages[focusedMessageIdx] - if (focusedMessage.role !== 'user') return; - if (!focusedMessage.state) return; + const focusedMessage = thread.messages[focusedMessageIdx]; + if (focusedMessage.role !== 'user') { return; } + if (!focusedMessage.state) { return; } - return focusedMessageIdx + return focusedMessageIdx; } isCurrentlyFocusingMessage() { - return this.getCurrentFocusedMessageIdx() !== undefined + return this.getCurrentFocusedMessageIdx() !== undefined; } switchToThread(threadId: string) { - this._setState({ currentThreadId: threadId }) + // Ensure the thread is in openTabs + const openTabs = this.state.openTabs.includes(threadId) + ? this.state.openTabs + : [...this.state.openTabs, threadId]; + this._setState({ currentThreadId: threadId, openTabs }); } openNewThread() { // if a thread with 0 messages already exists, switch to it - const { allThreads: currentThreads } = this.state + const { allThreads: currentThreads } = this.state; for (const threadId in currentThreads) { if (currentThreads[threadId]!.messages.length === 0) { // switch to the existing empty thread and exit - this.switchToThread(threadId) - return + this.switchToThread(threadId); + return; } } // otherwise, start a new thread - const newThread = newThreadObject() + const newThread = newThreadObject(); // update state const newThreads: ChatThreads = { ...currentThreads, [newThread.id]: newThread - } - this._storeAllThreads(newThreads) - this._setState({ allThreads: newThreads, currentThreadId: newThread.id }) + }; + const openTabs = this.state.openTabs.includes(newThread.id) + ? this.state.openTabs + : [...this.state.openTabs, newThread.id]; + this._storeAllThreads(newThreads); + this._setState({ allThreads: newThreads, currentThreadId: newThread.id, openTabs }); } deleteThread(threadId: string): void { - const { allThreads: currentThreads } = this.state + const { allThreads: currentThreads, openTabs, currentThreadId } = this.state; // delete the thread const newThreads = { ...currentThreads }; delete newThreads[threadId]; + // remove from openTabs if present + const newOpenTabs = openTabs.filter(id => id !== threadId); + + // if we deleted the current thread, switch to another tab or create a new one + let newCurrentThreadId = currentThreadId; + if (threadId === currentThreadId) { + if (newOpenTabs.length > 0) { + newCurrentThreadId = newOpenTabs[newOpenTabs.length - 1]; + } else { + // create a new thread + this.openNewThread(); + return; // openNewThread already updates state + } + } + // store the updated threads this._storeAllThreads(newThreads); - this._setState({ ...this.state, allThreads: newThreads }) + this._setState({ allThreads: newThreads, openTabs: newOpenTabs, currentThreadId: newCurrentThreadId }); } duplicateThread(threadId: string) { - const { allThreads: currentThreads } = this.state - const threadToDuplicate = currentThreads[threadId] - if (!threadToDuplicate) return + const { allThreads: currentThreads } = this.state; + const threadToDuplicate = currentThreads[threadId]; + if (!threadToDuplicate) { return; } const newThread = { ...deepClone(threadToDuplicate), id: generateUuid(), - } + }; const newThreads = { ...currentThreads, [newThread.id]: newThread, - } - this._storeAllThreads(newThreads) - this._setState({ allThreads: newThreads }) + }; + this._storeAllThreads(newThreads); + this._setState({ allThreads: newThreads }); } private _addMessageToThread(threadId: string, message: ChatMessage) { // Invalidate plan cache when plan messages are added if (message.role === 'plan') { - this._planCache.delete(threadId) + this._planCache.delete(threadId); } - const { allThreads } = this.state - const oldThread = allThreads[threadId] - if (!oldThread) return // should never happen + const { allThreads } = this.state; + const oldThread = allThreads[threadId]; + if (!oldThread) { return; } // should never happen // update state and store it const newThreads = { ...allThreads, @@ -4518,17 +4570,17 @@ We only need to do it for files that were edited since `from`, ie files between message ], } - } - this._storeAllThreads(newThreads) - this._setState({ allThreads: newThreads }) // the current thread just changed (it had a message added to it) + }; + this._storeAllThreads(newThreads); + this._setState({ allThreads: newThreads }); // the current thread just changed (it had a message added to it) } // sets the currently selected message (must be undefined if no message is selected) setCurrentlyFocusedMessageIdx(messageIdx: number | undefined) { - const threadId = this.state.currentThreadId - const thread = this.state.allThreads[threadId] - if (!thread) return + const threadId = this.state.currentThreadId; + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } this._setState({ allThreads: { @@ -4541,7 +4593,7 @@ We only need to do it for files that were edited since `from`, ie files between } } } - }) + }); // // when change focused message idx, jump - do not jump back when click edit, too confusing. // if (messageIdx !== undefined) @@ -4551,32 +4603,34 @@ We only need to do it for files that were edited since `from`, ie files between addNewStagingSelection(newSelection: StagingSelectionItem): void { - const focusedMessageIdx = this.getCurrentFocusedMessageIdx() + const focusedMessageIdx = this.getCurrentFocusedMessageIdx(); // set the selections to the proper value - let selections: StagingSelectionItem[] = [] - let setSelections = (s: StagingSelectionItem[]) => { } + let selections: StagingSelectionItem[] = []; + let setSelections = (s: StagingSelectionItem[]) => { }; if (focusedMessageIdx === undefined) { - selections = this.getCurrentThreadState().stagingSelections - setSelections = (s: StagingSelectionItem[]) => this.setCurrentThreadState({ stagingSelections: s }) + selections = this.getCurrentThreadState().stagingSelections; + setSelections = (s: StagingSelectionItem[]) => this.setCurrentThreadState({ stagingSelections: s }); } else { - selections = this.getCurrentMessageState(focusedMessageIdx).stagingSelections - setSelections = (s) => this.setCurrentMessageState(focusedMessageIdx, { stagingSelections: s }) + selections = this.getCurrentMessageState(focusedMessageIdx).stagingSelections; + setSelections = (s) => this.setCurrentMessageState(focusedMessageIdx, { stagingSelections: s }); } // if matches with existing selection, overwrite (since text may change) - const idx = findStagingSelectionIndex(selections, newSelection) - if (idx !== null && idx !== -1) { + const idx = findStagingSelectionIndex(selections, newSelection); + if (idx !== null) { + // idx is guaranteed to be >= 0 if not null + const currentSelections = selections ?? []; setSelections([ - ...selections!.slice(0, idx), + ...currentSelections.slice(0, idx), newSelection, - ...selections!.slice(idx + 1, Infinity) - ]) + ...currentSelections.slice(idx + 1, Infinity) + ]); } // if no match, add it else { - setSelections([...(selections ?? []), newSelection]) + setSelections([...(selections ?? []), newSelection]); } } @@ -4586,32 +4640,32 @@ We only need to do it for files that were edited since `from`, ie files between numPops = numPops ?? 1; - const focusedMessageIdx = this.getCurrentFocusedMessageIdx() + const focusedMessageIdx = this.getCurrentFocusedMessageIdx(); // set the selections to the proper value - let selections: StagingSelectionItem[] = [] - let setSelections = (s: StagingSelectionItem[]) => { } + let selections: StagingSelectionItem[] = []; + let setSelections = (s: StagingSelectionItem[]) => { }; if (focusedMessageIdx === undefined) { - selections = this.getCurrentThreadState().stagingSelections - setSelections = (s: StagingSelectionItem[]) => this.setCurrentThreadState({ stagingSelections: s }) + selections = this.getCurrentThreadState().stagingSelections; + setSelections = (s: StagingSelectionItem[]) => this.setCurrentThreadState({ stagingSelections: s }); } else { - selections = this.getCurrentMessageState(focusedMessageIdx).stagingSelections - setSelections = (s) => this.setCurrentMessageState(focusedMessageIdx, { stagingSelections: s }) + selections = this.getCurrentMessageState(focusedMessageIdx).stagingSelections; + setSelections = (s) => this.setCurrentMessageState(focusedMessageIdx, { stagingSelections: s }); } setSelections([ ...selections.slice(0, selections.length - numPops) - ]) + ]); } // set message.state private _setCurrentMessageState(state: Partial, messageIdx: number): void { - const threadId = this.state.currentThreadId - const thread = this.state.allThreads[threadId] - if (!thread) return + const threadId = this.state.currentThreadId; + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } this._setState({ allThreads: { @@ -4629,14 +4683,14 @@ We only need to do it for files that were edited since `from`, ie files between ) } } - }) + }); } // set thread.state private _setThreadState(threadId: string, state: Partial, doNotRefreshMountInfo?: boolean): void { - const thread = this.state.allThreads[threadId] - if (!thread) return + const thread = this.state.allThreads[threadId]; + if (!thread) { return; } this._setState({ allThreads: { @@ -4649,31 +4703,31 @@ We only need to do it for files that were edited since `from`, ie files between } } } - }, doNotRefreshMountInfo) + }, doNotRefreshMountInfo); } getCurrentThreadState = () => { - const currentThread = this.getCurrentThread() - return currentThread.state - } + const currentThread = this.getCurrentThread(); + return currentThread.state; + }; setCurrentThreadState = (newState: Partial) => { - this._setThreadState(this.state.currentThreadId, newState) - } + this._setThreadState(this.state.currentThreadId, newState); + }; // gets `staging` and `setStaging` of the currently focused element, given the index of the currently selected message (or undefined if no message is selected) getCurrentMessageState(messageIdx: number): UserMessageState { - const currMessage = this.getCurrentThread()?.messages?.[messageIdx] - if (!currMessage || currMessage.role !== 'user') return defaultMessageState - return currMessage.state + const currMessage = this.getCurrentThread()?.messages?.[messageIdx]; + if (!currMessage || currMessage.role !== 'user') { return defaultMessageState; } + return currMessage.state; } setCurrentMessageState(messageIdx: number, newState: Partial) { - const currMessage = this.getCurrentThread()?.messages?.[messageIdx] - if (!currMessage || currMessage.role !== 'user') return - this._setCurrentMessageState(newState, messageIdx) + const currMessage = this.getCurrentThread()?.messages?.[messageIdx]; + if (!currMessage || currMessage.role !== 'user') { return; } + this._setCurrentMessageState(newState, messageIdx); } diff --git a/src/vs/workbench/contrib/cortexide/browser/convertToLLMMessageService.ts b/src/vs/workbench/contrib/cortexide/browser/convertToLLMMessageService.ts index 3889cf0a3d4..91b7d7c7bd3 100644 --- a/src/vs/workbench/contrib/cortexide/browser/convertToLLMMessageService.ts +++ b/src/vs/workbench/contrib/cortexide/browser/convertToLLMMessageService.ts @@ -1,3 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + import { Disposable } from '../../../../base/common/lifecycle.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; @@ -69,7 +74,7 @@ import { IRepoIndexerService } from './repoIndexerService.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IMemoriesService } from '../common/memoriesService.js'; -export const EMPTY_MESSAGE = '(empty message)' +export const EMPTY_MESSAGE = '(empty message)'; @@ -87,39 +92,39 @@ type SimpleLLMMessage = { role: 'assistant'; content: string; anthropicReasoning: AnthropicReasoning[] | null; -} +}; -const CHARS_PER_TOKEN = 4 // assume abysmal chars per token -const TRIM_TO_LEN = 120 +const CHARS_PER_TOKEN = 4; // assume abysmal chars per token +const TRIM_TO_LEN = 120; // Safety clamp to avoid hitting provider TPM limits (e.g., OpenAI 30k TPM) // 20k tokens (~80k chars) gives more conservative headroom for output tokens and image tokens // Images can add significant tokens (~85 per 512x512 tile), so we need more headroom -const MAX_INPUT_TOKENS_SAFETY = 20_000 +const MAX_INPUT_TOKENS_SAFETY = 20_000; // Estimate tokens for images in OpenAI format // OpenAI uses ~85 tokens per 512x512 tile, plus base overhead // For detailed images, tokens scale with image dimensions // Reference: https://platform.openai.com/docs/guides/vision#calculating-costs const estimateImageTokens = (images: ChatImageAttachment[] | undefined): number => { - if (!images || images.length === 0) return 0 - let totalTokens = 0 + if (!images || images.length === 0) { return 0; } + let totalTokens = 0; for (const img of images) { // Base overhead per image: ~85 tokens - totalTokens += 85 + totalTokens += 85; // Estimate tokens based on image dimensions // Images are resized to fit within 2048x2048, then scaled so shortest side is 768px // Each 512x512 tile costs ~170 tokens (85 for base + 85 for detail) // For a rough estimate, use image size as a proxy // Base64 encoding increases size by ~33%, so we estimate conservatively - const base64Size = Math.ceil((img.size || img.data.length) * 1.33) + const base64Size = Math.ceil((img.size || img.data.length) * 1.33); // Very rough estimate: ~1 token per 100 bytes of base64 (conservative) // This accounts for the fact that images are tokenized more efficiently than text - totalTokens += Math.ceil(base64Size / 100) + totalTokens += Math.ceil(base64Size / 100); } - return totalTokens -} + return totalTokens; +}; @@ -147,16 +152,21 @@ openai on developer system message - https://cdn.openai.com/spec/model-spec-2024 */ -const prepareMessages_openai_tools = (messages: SimpleLLMMessage[]): AnthropicOrOpenAILLMMessage[] => { +const prepareMessages_openai_tools = (messages: SimpleLLMMessage[], providerName?: ProviderName): AnthropicOrOpenAILLMMessage[] => { - const newMessages: OpenAILLMChatMessage[] = []; + const newMessages: AnthropicOrOpenAILLMMessage[] = []; + + // v0.dev uses Anthropic-style image format even though it's OpenAI-compatible for tools + // Check if this is v0 by checking if providerName is openAICompatible + // (We can make this more specific later if needed, e.g., by checking endpoint) + const useAnthropicImageFormat = providerName === 'openAICompatible'; for (let i = 0; i < messages.length; i += 1) { - const currMsg = messages[i] + const currMsg = messages[i]; if (currMsg.role === 'user') { - // Convert images to OpenAI format if present - const contentParts: Array<{ type: 'text'; text: string } | { type: 'image_url'; image_url: { url: string } }> = []; + // Convert images to OpenAI format if present (or Anthropic format for v0) + const contentParts: Array<{ type: 'text'; text: string } | { type: 'image_url'; image_url: { url: string } } | { type: 'image'; source: { type: 'base64'; media_type: 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif'; data: string } }> = []; const hasImages = currMsg.images && currMsg.images.length > 0; // Prepare text content @@ -208,7 +218,7 @@ const prepareMessages_openai_tools = (messages: SimpleLLMMessage[]): AnthropicOr // Ensure image.data is a Uint8Array // TypeScript knows image.data is Uint8Array from the type definition, but we validate at runtime // Use 'any' to bypass TypeScript's type narrowing for runtime validation - const data: any = image.data; + const data: unknown = image.data; let imageData: Uint8Array; if (data instanceof Uint8Array) { @@ -289,7 +299,7 @@ const prepareMessages_openai_tools = (messages: SimpleLLMMessage[]): AnthropicOr for (let i = start; i < end; i++) { // Use hasOwnProperty check to avoid getters/prototype issues if (Object.prototype.hasOwnProperty.call(data, String(i))) { - const val = (data as any)[String(i)]; + const val = (data as unknown)[String(i)]; if (typeof val === 'number' && val >= 0 && val <= 255 && Number.isInteger(val)) { values.push(val); } else if (val !== undefined && val !== null) { @@ -369,7 +379,7 @@ const prepareMessages_openai_tools = (messages: SimpleLLMMessage[]): AnthropicOr } // Use VS Code's built-in base64 encoder (already tested and optimized) - let base64 = uint8ArrayToBase64(imageData); + const base64 = uint8ArrayToBase64(imageData); // Validate base64 format - must contain only valid base64 characters // OpenAI is strict: base64 must be clean, no whitespace, proper padding @@ -392,61 +402,95 @@ const prepareMessages_openai_tools = (messages: SimpleLLMMessage[]): AnthropicOr throw new Error('Invalid base64 encoding: too many padding characters'); } - // Construct data URL - OpenAI expects format: data:image/;base64, - // Ensure no whitespace in the final URL - const dataUrl = `data:${mimeType};base64,${base64}`.trim(); + // v0.dev uses Anthropic-style image format instead of OpenAI format + if (useAnthropicImageFormat) { + // Use Anthropic format: { type: 'image', source: { type: 'base64', media_type, data } } + contentParts.push({ + type: 'image', + source: { + type: 'base64', + media_type: mimeType, + data: base64, + }, + } as unknown); // Type assertion needed because contentParts type is union + } else { + // Use OpenAI format: { type: 'image_url', image_url: { url: dataUrl } } + // Construct data URL - OpenAI expects format: data:image/;base64, + // Ensure no whitespace in the final URL + const dataUrl = `data:${mimeType};base64,${base64}`.trim(); + + // Additional validation: ensure data URL is reasonable size + if (dataUrl.length > 30 * 1024 * 1024) { // 30MB as safety limit + console.error('Data URL too large:', dataUrl.length); + throw new Error('Image data URL is too large'); + } - // Additional validation: ensure data URL is reasonable size - if (dataUrl.length > 30 * 1024 * 1024) { // 30MB as safety limit - console.error('Data URL too large:', dataUrl.length); - throw new Error('Image data URL is too large'); + contentParts.push({ + type: 'image_url', + image_url: { url: dataUrl }, + }); } - - contentParts.push({ - type: 'image_url', - image_url: { url: dataUrl }, - }); } } // Use array format if we have images or multiple parts, otherwise use string // For OpenAI, if we have images, we MUST use array format with at least text + images - const userMsg: OpenAILLMChatMessage = { - role: 'user', - content: hasImages ? contentParts : (contentParts.length > 0 ? contentParts : (textContent || '')), - }; - newMessages.push(userMsg); - continue + // For v0 (Anthropic format), we also need array format + if (useAnthropicImageFormat && hasImages) { + // For Anthropic format, ensure contentParts only contains valid Anthropic types + const anthropicContentParts: Array<{ type: 'text'; text: string } | { type: 'image'; source: { type: 'base64'; media_type: 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif'; data: string } }> = contentParts.filter((part): part is { type: 'text'; text: string } | { type: 'image'; source: { type: 'base64'; media_type: 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif'; data: string } } => + part.type === 'text' || part.type === 'image' + ) as unknown; + const userMsg: AnthropicLLMChatMessage = { + role: 'user', + content: anthropicContentParts.length > 0 ? anthropicContentParts : (textContent || ''), + }; + newMessages.push(userMsg); + } else { + // For OpenAI format, ensure contentParts only contains valid OpenAI types + const openAIContentParts: Array<{ type: 'text'; text: string } | { type: 'image_url'; image_url: { url: string } }> = contentParts.filter((part): part is { type: 'text'; text: string } | { type: 'image_url'; image_url: { url: string } } => + part.type === 'text' || part.type === 'image_url' + ) as unknown; + const userMsg: OpenAILLMChatMessage = { + role: 'user', + content: hasImages ? openAIContentParts : (openAIContentParts.length > 0 ? openAIContentParts : (textContent || '')), + }; + newMessages.push(userMsg); + } + continue; } if (currMsg.role !== 'tool') { - newMessages.push(currMsg as OpenAILLMChatMessage) - continue + newMessages.push(currMsg as AnthropicOrOpenAILLMMessage); + continue; } // edit previous assistant message to have called the tool - const prevMsg = 0 <= i - 1 && i - 1 <= newMessages.length ? newMessages[i - 1] : undefined + // Note: This function is for OpenAI-style tools, so assistant messages are always OpenAI format + const prevMsg = 0 <= i - 1 && i - 1 <= newMessages.length ? newMessages[i - 1] : undefined; if (prevMsg?.role === 'assistant') { - prevMsg.tool_calls = [{ + // Type assertion: In prepareMessages_openai_tools, assistant messages are always OpenAI format + const openAIMsg = prevMsg as Extract; + openAIMsg.tool_calls = [{ type: 'function', id: currMsg.id, function: { name: currMsg.name, arguments: JSON.stringify(currMsg.rawParams) } - }] + }]; } - // add the tool + // add the tool (OpenAI format) newMessages.push({ role: 'tool', tool_call_id: currMsg.id, content: currMsg.content, - }) + } as OpenAILLMChatMessage); } - return newMessages + return newMessages; -} +}; @@ -479,31 +523,31 @@ assistant: ...content, call(name, id, params) user: ...content, result(id, content) */ -type AnthropicOrOpenAILLMMessage = AnthropicLLMChatMessage | OpenAILLMChatMessage +type AnthropicOrOpenAILLMMessage = AnthropicLLMChatMessage | OpenAILLMChatMessage; const prepareMessages_anthropic_tools = (messages: SimpleLLMMessage[], supportsAnthropicReasoning: boolean): AnthropicOrOpenAILLMMessage[] => { const newMessages: (AnthropicLLMChatMessage | (SimpleLLMMessage & { role: 'tool' }))[] = messages; for (let i = 0; i < messages.length; i += 1) { - const currMsg = messages[i] + const currMsg = messages[i]; // add anthropic reasoning if (currMsg.role === 'assistant') { if (currMsg.anthropicReasoning && supportsAnthropicReasoning) { - const content = currMsg.content + const content = currMsg.content; newMessages[i] = { role: 'assistant', content: content ? [...currMsg.anthropicReasoning, { type: 'text' as const, text: content }] : currMsg.anthropicReasoning - } + }; } else { newMessages[i] = { role: 'assistant', content: currMsg.content, // strip away anthropicReasoning - } + }; } - continue + continue; } if (currMsg.role === 'user') { @@ -546,8 +590,8 @@ const prepareMessages_anthropic_tools = (messages: SimpleLLMMessage[], supportsA // Anthropic SDK expects specific MIME types, cast appropriately const mediaType: 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif' = image.mimeType === 'image/svg+xml' ? 'image/png' : - (image.mimeType === 'image/png' || image.mimeType === 'image/jpeg' || image.mimeType === 'image/webp' || image.mimeType === 'image/gif' - ? image.mimeType : 'image/png'); + (image.mimeType === 'image/png' || image.mimeType === 'image/jpeg' || image.mimeType === 'image/webp' || image.mimeType === 'image/gif' + ? image.mimeType : 'image/png'); contentParts.push({ type: 'image', source: { @@ -566,32 +610,32 @@ const prepareMessages_anthropic_tools = (messages: SimpleLLMMessage[], supportsA content: hasImages ? contentParts : (contentParts.length > 0 ? contentParts : (textContent || '')), }; newMessages[i] = userMsg; - continue + continue; } if (currMsg.role === 'tool') { // add anthropic tools - const prevMsg = 0 <= i - 1 && i - 1 <= newMessages.length ? newMessages[i - 1] : undefined + const prevMsg = 0 <= i - 1 && i - 1 <= newMessages.length ? newMessages[i - 1] : undefined; // make it so the assistant called the tool if (prevMsg?.role === 'assistant') { - if (typeof prevMsg.content === 'string') prevMsg.content = [{ type: 'text', text: prevMsg.content }] - prevMsg.content.push({ type: 'tool_use', id: currMsg.id, name: currMsg.name, input: currMsg.rawParams }) + if (typeof prevMsg.content === 'string') { prevMsg.content = [{ type: 'text', text: prevMsg.content }]; } + prevMsg.content.push({ type: 'tool_use', id: currMsg.id, name: currMsg.name, input: currMsg.rawParams }); } // turn each tool into a user message with tool results at the end newMessages[i] = { role: 'user', content: [{ type: 'tool_result', tool_use_id: currMsg.id, content: currMsg.content }] - } - continue + }; + continue; } } // we just removed the tools - return newMessages as AnthropicLLMChatMessage[] -} + return newMessages as AnthropicLLMChatMessage[]; +}; const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthropicReasoning: boolean): AnthropicOrOpenAILLMMessage[] => { @@ -599,30 +643,29 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop const llmChatMessages: AnthropicOrOpenAILLMMessage[] = []; for (let i = 0; i < messages.length; i += 1) { - const c = messages[i] - const next = 0 <= i + 1 && i + 1 <= messages.length - 1 ? messages[i + 1] : null + const c = messages[i]; + const next = 0 <= i + 1 && i + 1 <= messages.length - 1 ? messages[i + 1] : null; if (c.role === 'assistant') { // if called a tool (message after it), re-add its XML to the message // alternatively, could just hold onto the original output, but this way requires less piping raw strings everywhere - let content: AnthropicOrOpenAILLMMessage['content'] = c.content + let content: AnthropicOrOpenAILLMMessage['content'] = c.content; if (next?.role === 'tool') { - content = `${content}\n\n${reParsedToolXMLString(next.name, next.rawParams)}` + content = `${content}\n\n${reParsedToolXMLString(next.name, next.rawParams)}`; } // anthropic reasoning if (c.anthropicReasoning && supportsAnthropicReasoning) { - content = content ? [...c.anthropicReasoning, { type: 'text' as const, text: content }] : c.anthropicReasoning + content = content ? [...c.anthropicReasoning, { type: 'text' as const, text: content }] : c.anthropicReasoning; } llmChatMessages.push({ role: 'assistant', content - }) + }); } // add user or tool to the previous user message else if (c.role === 'user' || c.role === 'tool') { - if (c.role === 'tool') - c.content = `<${c.name}_result>\n${c.content}\n` + if (c.role === 'tool') { c.content = `<${c.name}_result>\n${c.content}\n`; } if (llmChatMessages.length === 0 || llmChatMessages[llmChatMessages.length - 1].role !== 'user') { // Convert images to Anthropic format if present (only for user messages) @@ -664,8 +707,8 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop // Anthropic SDK expects specific MIME types, cast appropriately const mediaType: 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif' = image.mimeType === 'image/svg+xml' ? 'image/png' : - (image.mimeType === 'image/png' || image.mimeType === 'image/jpeg' || image.mimeType === 'image/webp' || image.mimeType === 'image/gif' - ? image.mimeType : 'image/png'); + (image.mimeType === 'image/png' || image.mimeType === 'image/jpeg' || image.mimeType === 'image/webp' || image.mimeType === 'image/gif' + ? image.mimeType : 'image/png'); contentParts.push({ type: 'image', source: { @@ -700,8 +743,8 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop const base64 = uint8ArrayToBase64(image.data); const mediaType: 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif' = image.mimeType === 'image/svg+xml' ? 'image/png' : - (image.mimeType === 'image/png' || image.mimeType === 'image/jpeg' || image.mimeType === 'image/webp' || image.mimeType === 'image/gif' - ? image.mimeType : 'image/png'); + (image.mimeType === 'image/png' || image.mimeType === 'image/jpeg' || image.mimeType === 'image/webp' || image.mimeType === 'image/gif' + ? image.mimeType : 'image/png'); contentArray.push({ type: 'image', source: { @@ -711,7 +754,7 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop }, }); } - lastMsg.content = contentArray as any; + lastMsg.content = contentArray as unknown; } else { // No images, just append text lastMsg.content += '\n\n' + c.content; @@ -737,8 +780,8 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop // Anthropic SDK expects specific MIME types, cast appropriately const mediaType: 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif' = image.mimeType === 'image/svg+xml' ? 'image/png' : - (image.mimeType === 'image/png' || image.mimeType === 'image/jpeg' || image.mimeType === 'image/webp' || image.mimeType === 'image/gif' - ? image.mimeType : 'image/png'); + (image.mimeType === 'image/png' || image.mimeType === 'image/jpeg' || image.mimeType === 'image/webp' || image.mimeType === 'image/gif' + ? image.mimeType : 'image/png'); contentArray.push({ type: 'image', source: { @@ -754,8 +797,8 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop } } } - return llmChatMessages -} + return llmChatMessages; +}; // --- CHAT --- @@ -769,97 +812,99 @@ const prepareOpenAIOrAnthropicMessages = ({ supportsAnthropicReasoning, contextWindow, reservedOutputTokenSpace, + providerName, }: { - messages: SimpleLLMMessage[], - systemMessage: string, - aiInstructions: string, - supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated', - specialToolFormat: 'openai-style' | 'anthropic-style' | undefined, - supportsAnthropicReasoning: boolean, - contextWindow: number, - reservedOutputTokenSpace: number | null | undefined, -}): { messages: AnthropicOrOpenAILLMMessage[], separateSystemMessage: string | undefined } => { + messages: SimpleLLMMessage[]; + systemMessage: string; + aiInstructions: string; + supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated'; + specialToolFormat: 'openai-style' | 'anthropic-style' | undefined; + supportsAnthropicReasoning: boolean; + contextWindow: number; + reservedOutputTokenSpace: number | null | undefined; + providerName: ProviderName; +}): { messages: AnthropicOrOpenAILLMMessage[]; separateSystemMessage: string | undefined } => { reservedOutputTokenSpace = Math.max( contextWindow * 1 / 2, // reserve at least 1/4 of the token window length reservedOutputTokenSpace ?? 4_096 // defaults to 4096 - ) + ); // Optimized: shallow clone + selective deep clone only for mutable fields // Images (Uint8Array) are large and don't need cloning since we won't mutate them - let messages: (SimpleLLMMessage | { role: 'system', content: string })[] = messages_.map(msg => { + let messages: (SimpleLLMMessage | { role: 'system'; content: string })[] = messages_.map(msg => { if (msg.role === 'user' && msg.images) { // Shallow clone but keep images reference (we don't mutate images) - return { ...msg, images: msg.images } + return { ...msg, images: msg.images }; } // For other messages, shallow clone is sufficient since content is string - return { ...msg } - }) as (SimpleLLMMessage | { role: 'system', content: string })[] + return { ...msg }; + }) as (SimpleLLMMessage | { role: 'system'; content: string })[]; // ================ system message ================ // A COMPLETE HACK: last message is system message for context purposes - const sysMsgParts: string[] = [] - if (aiInstructions) sysMsgParts.push(`GUIDELINES (from the user's .voidrules file):\n${aiInstructions}`) - if (systemMessage) sysMsgParts.push(systemMessage) - const combinedSystemMessage = sysMsgParts.join('\n\n') + const sysMsgParts: string[] = []; + if (aiInstructions) { sysMsgParts.push(`GUIDELINES (from the user's .voidrules file):\n${aiInstructions}`); } + if (systemMessage) { sysMsgParts.push(systemMessage); } + const combinedSystemMessage = sysMsgParts.join('\n\n'); - messages.unshift({ role: 'system', content: combinedSystemMessage }) + messages.unshift({ role: 'system', content: combinedSystemMessage }); // ================ trim ================ - messages = messages.map(m => ({ ...m, content: m.role !== 'tool' ? m.content.trim() : m.content })) + messages = messages.map(m => ({ ...m, content: m.role !== 'tool' ? m.content.trim() : m.content })); - type MesType = (typeof messages)[0] + type MesType = (typeof messages)[0]; - // ================ fit into context ================ + // ================ fit into context ================ // the higher the weight, the higher the desire to truncate - TRIM HIGHEST WEIGHT MESSAGES - const alreadyTrimmedIdxes = new Set() + const alreadyTrimmedIdxes = new Set(); const weight = (message: MesType, messages: MesType[], idx: number) => { - const base = message.content.length + const base = message.content.length; - let multiplier: number - multiplier = 1 + (messages.length - 1 - idx) / messages.length // slow rampdown from 2 to 1 as index increases + let multiplier: number; + multiplier = 1 + (messages.length - 1 - idx) / messages.length; // slow rampdown from 2 to 1 as index increases if (message.role === 'user') { - multiplier *= 1 + multiplier *= 1; } else if (message.role === 'system') { - multiplier *= .01 // very low weight + multiplier *= .01; // very low weight } else { - multiplier *= 10 // llm tokens are far less valuable than user tokens + multiplier *= 10; // llm tokens are far less valuable than user tokens } // any already modified message should not be trimmed again if (alreadyTrimmedIdxes.has(idx)) { - multiplier = 0 + multiplier = 0; } // 1st and last messages should be very low weight if (idx <= 1 || idx >= messages.length - 1 - 3) { - multiplier *= .05 + multiplier *= .05; } - return base * multiplier - } + return base * multiplier; + }; const _findLargestByWeight = (messages_: MesType[]) => { - let largestIndex = -1 - let largestWeight = -Infinity + let largestIndex = -1; + let largestWeight = -Infinity; for (let i = 0; i < messages.length; i += 1) { - const m = messages[i] - const w = weight(m, messages_, i) + const m = messages[i]; + const w = weight(m, messages_, i); if (w > largestWeight) { - largestWeight = w - largestIndex = i + largestWeight = w; + largestIndex = i; } } - return largestIndex - } + return largestIndex; + }; - let totalLen = 0 - for (const m of messages) { totalLen += m.content.length } + let totalLen = 0; + for (const m of messages) { totalLen += m.content.length; } const charsNeedToTrim = totalLen - Math.max( (contextWindow - reservedOutputTokenSpace) * CHARS_PER_TOKEN, // can be 0, in which case charsNeedToTrim=everything, bad 5_000 // ensure we don't trim at least 5k chars (just a random small value) - ) + ); // <-----------------------------------------> @@ -867,27 +912,27 @@ const prepareOpenAIOrAnthropicMessages = ({ // | contextWindow | // contextWindow - maxOut|putTokens // totalLen - let remainingCharsToTrim = charsNeedToTrim - let i = 0 + let remainingCharsToTrim = charsNeedToTrim; + let i = 0; while (remainingCharsToTrim > 0) { - i += 1 - if (i > 100) break + i += 1; + if (i > 100) { break; } - const trimIdx = _findLargestByWeight(messages) - const m = messages[trimIdx] + const trimIdx = _findLargestByWeight(messages); + const m = messages[trimIdx]; // if can finish here, do - const numCharsWillTrim = m.content.length - TRIM_TO_LEN + const numCharsWillTrim = m.content.length - TRIM_TO_LEN; if (numCharsWillTrim > remainingCharsToTrim) { // trim remainingCharsToTrim + '...'.length chars - m.content = m.content.slice(0, m.content.length - remainingCharsToTrim - '...'.length).trim() + '...' - break + m.content = m.content.slice(0, m.content.length - remainingCharsToTrim - '...'.length).trim() + '...'; + break; } - remainingCharsToTrim -= numCharsWillTrim - m.content = m.content.substring(0, TRIM_TO_LEN - '...'.length) + '...' - alreadyTrimmedIdxes.add(trimIdx) + remainingCharsToTrim -= numCharsWillTrim; + m.content = m.content.substring(0, TRIM_TO_LEN - '...'.length) + '...'; + alreadyTrimmedIdxes.add(trimIdx); } // ================ safety clamp to avoid TPM overage ================ @@ -895,91 +940,88 @@ const prepareOpenAIOrAnthropicMessages = ({ // This accounts for text tokens, image tokens, system messages, tool definitions, and message structure overhead const safetyTrim = () => { // Estimate total tokens: text content + images + system message overhead - let textChars = 0 - let imageTokens = 0 + let textChars = 0; + let imageTokens = 0; for (const m of messages) { - textChars += m.content.length + textChars += m.content.length; // Check if message has images (SimpleLLMMessage with images property) if ('images' in m && m.images) { - imageTokens += estimateImageTokens(m.images) + imageTokens += estimateImageTokens(m.images); } } // Add system message tokens (will be added separately or prepended) - const systemMessageTokens = Math.ceil(combinedSystemMessage.length / CHARS_PER_TOKEN) + const systemMessageTokens = Math.ceil(combinedSystemMessage.length / CHARS_PER_TOKEN); // Message structure overhead: JSON formatting, role names, etc. // Estimate ~8 tokens per message for structure (role, content wrapper, etc.) - const messageStructureOverhead = messages.length * 8 + const messageStructureOverhead = messages.length * 8; // Native tool definitions overhead (when using openai-style, tools are sent separately) // Conservative estimate: ~500-2000 tokens depending on number of tools // Since we don't have tool info here, use a conservative buffer - const nativeToolDefinitionsOverhead = specialToolFormat === 'openai-style' ? 1000 : 0 + const nativeToolDefinitionsOverhead = specialToolFormat === 'openai-style' ? 1000 : 0; // Total estimated tokens - const textTokens = Math.ceil(textChars / CHARS_PER_TOKEN) - const totalEstimatedTokens = textTokens + imageTokens + systemMessageTokens + messageStructureOverhead + nativeToolDefinitionsOverhead + const textTokens = Math.ceil(textChars / CHARS_PER_TOKEN); + const totalEstimatedTokens = textTokens + imageTokens + systemMessageTokens + messageStructureOverhead + nativeToolDefinitionsOverhead; // If we're under the limit, no need to trim - if (totalEstimatedTokens <= MAX_INPUT_TOKENS_SAFETY) return + if (totalEstimatedTokens <= MAX_INPUT_TOKENS_SAFETY) { return; } // Need to trim more aggressively - const excessTokens = totalEstimatedTokens - MAX_INPUT_TOKENS_SAFETY - const excessChars = excessTokens * CHARS_PER_TOKEN + const excessTokens = totalEstimatedTokens - MAX_INPUT_TOKENS_SAFETY; + const excessChars = excessTokens * CHARS_PER_TOKEN; - let guardLoops = 0 - let charsTrimmed = 0 + let guardLoops = 0; + let charsTrimmed = 0; while (charsTrimmed < excessChars && guardLoops < 200) { - guardLoops += 1 - const trimIdx = _findLargestByWeight(messages) - const m = messages[trimIdx] + guardLoops += 1; + const trimIdx = _findLargestByWeight(messages); + const m = messages[trimIdx]; if (m.content.length <= TRIM_TO_LEN) { // Already tiny, skip to next largest - alreadyTrimmedIdxes.add(trimIdx) - continue + alreadyTrimmedIdxes.add(trimIdx); + continue; } - const before = m.content.length - m.content = m.content.substring(0, TRIM_TO_LEN - '...'.length) + '...' - alreadyTrimmedIdxes.add(trimIdx) - charsTrimmed += (before - m.content.length) + const before = m.content.length; + m.content = m.content.substring(0, TRIM_TO_LEN - '...'.length) + '...'; + alreadyTrimmedIdxes.add(trimIdx); + charsTrimmed += (before - m.content.length); } - } + }; - safetyTrim() + safetyTrim(); // ================ system message hack ================ - const newSysMsg = messages.shift()!.content + const newSysMsg = messages.shift()!.content; // ================ tools and anthropicReasoning ================ // SYSTEM MESSAGE HACK: we shifted (removed) the system message role, so now SimpleLLMMessage[] is valid - let llmChatMessages: AnthropicOrOpenAILLMMessage[] = [] + let llmChatMessages: AnthropicOrOpenAILLMMessage[] = []; if (!specialToolFormat) { // XML tool behavior - llmChatMessages = prepareMessages_XML_tools(messages as SimpleLLMMessage[], supportsAnthropicReasoning) + llmChatMessages = prepareMessages_XML_tools(messages as SimpleLLMMessage[], supportsAnthropicReasoning); } else if (specialToolFormat === 'anthropic-style') { - llmChatMessages = prepareMessages_anthropic_tools(messages as SimpleLLMMessage[], supportsAnthropicReasoning) + llmChatMessages = prepareMessages_anthropic_tools(messages as SimpleLLMMessage[], supportsAnthropicReasoning); } else if (specialToolFormat === 'openai-style') { - llmChatMessages = prepareMessages_openai_tools(messages as SimpleLLMMessage[]) + llmChatMessages = prepareMessages_openai_tools(messages as SimpleLLMMessage[], providerName); } - const llmMessages = llmChatMessages + const llmMessages = llmChatMessages; // ================ system message add as first llmMessage ================ - let separateSystemMessageStr: string | undefined = undefined + let separateSystemMessageStr: string | undefined = undefined; // if supports system message if (supportsSystemMessage) { - if (supportsSystemMessage === 'separated') - separateSystemMessageStr = newSysMsg - else if (supportsSystemMessage === 'system-role') - llmMessages.unshift({ role: 'system', content: newSysMsg }) // add new first message - else if (supportsSystemMessage === 'developer-role') - llmMessages.unshift({ role: 'developer', content: newSysMsg }) // add new first message + if (supportsSystemMessage === 'separated') { separateSystemMessageStr = newSysMsg; } + else if (supportsSystemMessage === 'system-role') { llmMessages.unshift({ role: 'system', content: newSysMsg }); } // add new first message + else if (supportsSystemMessage === 'developer-role') { llmMessages.unshift({ role: 'developer', content: newSysMsg }); } // add new first message } // if does not support system message else { @@ -995,53 +1037,53 @@ const prepareOpenAIOrAnthropicMessages = ({ llmMessages.splice(0, 1); // delete first message llmMessages.unshift(newFirstMessage); // add new first message } else { - // Content is an array (may contain images/text parts) - // Prepend system message to the first text part, or add a new text part - const contentArray = [...firstMsg.content] as any[]; - const firstTextIndex = contentArray.findIndex((c: any) => c.type === 'text'); - - if (firstTextIndex !== -1) { - // Prepend to existing text part - contentArray[firstTextIndex] = { - type: 'text', - text: systemMsgPrefix + (contentArray[firstTextIndex] as any).text - }; - } else { - // No text part exists, add one at the beginning - contentArray.unshift({ - type: 'text', - text: systemMsgPrefix.trim() - }); - } + // Content is an array (may contain images/text parts) + // Prepend system message to the first text part, or add a new text part + const contentArray = [...firstMsg.content] as unknown[]; + const firstTextIndex = contentArray.findIndex((c: unknown) => c.type === 'text'); + + if (firstTextIndex !== -1) { + // Prepend to existing text part + contentArray[firstTextIndex] = { + type: 'text', + text: systemMsgPrefix + (contentArray[firstTextIndex] as unknown).text + }; + } else { + // No text part exists, add one at the beginning + contentArray.unshift({ + type: 'text', + text: systemMsgPrefix.trim() + }); + } - const newFirstMessage: AnthropicOrOpenAILLMMessage = { - role: 'user', - content: contentArray - }; - llmMessages.splice(0, 1); // delete first message - llmMessages.unshift(newFirstMessage); // add new first message + const newFirstMessage: AnthropicOrOpenAILLMMessage = { + role: 'user', + content: contentArray + }; + llmMessages.splice(0, 1); // delete first message + llmMessages.unshift(newFirstMessage); // add new first message } } // ================ no empty message ================ for (let i = 0; i < llmMessages.length; i += 1) { - const currMsg: AnthropicOrOpenAILLMMessage = llmMessages[i] - const nextMsg: AnthropicOrOpenAILLMMessage | undefined = llmMessages[i + 1] + const currMsg: AnthropicOrOpenAILLMMessage = llmMessages[i]; + const nextMsg: AnthropicOrOpenAILLMMessage | undefined = llmMessages[i + 1]; - if (currMsg.role === 'tool') continue + if (currMsg.role === 'tool') { continue; } // if content is a string, replace string with empty msg if (typeof currMsg.content === 'string') { - currMsg.content = currMsg.content || EMPTY_MESSAGE + currMsg.content = currMsg.content || EMPTY_MESSAGE; } else { // allowed to be empty if has a tool in it or following it if (currMsg.content.find(c => c.type === 'tool_result' || c.type === 'tool_use')) { - currMsg.content = currMsg.content.filter(c => !(c.type === 'text' && !c.text)) as any - continue + currMsg.content = currMsg.content.filter(c => !(c.type === 'text' && !c.text)) as unknown; + continue; } - if (nextMsg?.role === 'tool') continue + if (nextMsg?.role === 'tool') { continue; } // Check if we have images in the content array const hasImagesInContent = currMsg.content.some(c => c.type === 'image' || c.type === 'image_url'); @@ -1054,67 +1096,67 @@ const prepareOpenAIOrAnthropicMessages = ({ if (textPartIndex === -1) { // No text part exists, add one at the beginning - currMsg.content.unshift({ type: 'text', text: imageAnalysisPrompt } as any); + currMsg.content.unshift({ type: 'text', text: imageAnalysisPrompt } as unknown); } else { // Text part exists, ensure it's not empty const textPart = currMsg.content[textPartIndex]; if (textPart.type === 'text' && (!textPart.text || textPart.text.trim() === '' || textPart.text === EMPTY_MESSAGE)) { // Replace empty text with proper image analysis prompt - currMsg.content[textPartIndex] = { type: 'text', text: imageAnalysisPrompt } as any; + currMsg.content[textPartIndex] = { type: 'text', text: imageAnalysisPrompt } as unknown; } } } else { // No images, just replace empty text with EMPTY_MESSAGE for (const c of currMsg.content) { - if (c.type === 'text') c.text = c.text || EMPTY_MESSAGE + if (c.type === 'text') { c.text = c.text || EMPTY_MESSAGE; } } } // If array is completely empty, add a text entry - if (currMsg.content.length === 0) currMsg.content = [{ type: 'text', text: EMPTY_MESSAGE }] + if (currMsg.content.length === 0) { currMsg.content = [{ type: 'text', text: EMPTY_MESSAGE }]; } } } return { messages: llmMessages, separateSystemMessage: separateSystemMessageStr, - } as const -} + } as const; +}; -type GeminiUserPart = (GeminiLLMChatMessage & { role: 'user' })['parts'][0] -type GeminiModelPart = (GeminiLLMChatMessage & { role: 'model' })['parts'][0] +type GeminiUserPart = (GeminiLLMChatMessage & { role: 'user' })['parts'][0]; +type GeminiModelPart = (GeminiLLMChatMessage & { role: 'model' })['parts'][0]; const prepareGeminiMessages = (messages: AnthropicLLMChatMessage[]) => { - let latestToolName: ToolName | undefined = undefined + let latestToolName: ToolName | undefined = undefined; const messages2: GeminiLLMChatMessage[] = messages.map((m): GeminiLLMChatMessage | null => { if (m.role === 'assistant') { if (typeof m.content === 'string') { - return { role: 'model', parts: [{ text: m.content }] } + return { role: 'model', parts: [{ text: m.content }] }; } else { const parts: GeminiModelPart[] = m.content.map((c): GeminiModelPart | null => { if (c.type === 'text') { - return { text: c.text } + return { text: c.text }; } else if (c.type === 'tool_use') { - latestToolName = c.name - return { functionCall: { id: c.id, name: c.name, args: c.input } } + latestToolName = c.name; + return { functionCall: { id: c.id, name: c.name, args: c.input } }; } - else return null - }).filter(m => !!m) - return { role: 'model', parts, } + else { return null; } + }).filter(m => !!m); + return { role: 'model', parts, }; } } else if (m.role === 'user') { if (typeof m.content === 'string') { - return { role: 'user', parts: [{ text: m.content }] } satisfies GeminiLLMChatMessage + return { role: 'user', parts: [{ text: m.content }] } satisfies GeminiLLMChatMessage; } else { const parts: GeminiUserPart[] = m.content.map((c): GeminiUserPart | null => { if (c.type === 'text') { - return { text: c.text } + return { text: c.text }; } else if (c.type === 'image') { // Convert Anthropic image format to Gemini inlineData format @@ -1123,14 +1165,14 @@ const prepareGeminiMessages = (messages: AnthropicLLMChatMessage[]) => { mimeType: c.source.media_type, data: c.source.data, }, - } + }; } else if (c.type === 'tool_result') { - if (!latestToolName) return null - return { functionResponse: { id: c.tool_use_id, name: latestToolName, response: { output: c.content } } } + if (!latestToolName) { return null; } + return { functionResponse: { id: c.tool_use_id, name: latestToolName, response: { output: c.content } } }; } - else return null - }).filter(m => !!m) + else { return null; } + }).filter(m => !!m); // Ensure we have at least one part, and if we have images, ensure we have text const hasImages = parts.some(p => 'inlineData' in p); @@ -1143,51 +1185,51 @@ const prepareGeminiMessages = (messages: AnthropicLLMChatMessage[]) => { parts.unshift({ text: '(empty message)' }); } - return { role: 'user', parts, } + return { role: 'user', parts, }; } } - else return null - }).filter(m => !!m) + else { return null; } + }).filter(m => !!m); - return messages2 -} + return messages2; +}; const prepareMessages = (params: { - messages: SimpleLLMMessage[], - systemMessage: string, - aiInstructions: string, - supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated', - specialToolFormat: 'openai-style' | 'anthropic-style' | 'gemini-style' | undefined, - supportsAnthropicReasoning: boolean, - contextWindow: number, - reservedOutputTokenSpace: number | null | undefined, - providerName: ProviderName -}): { messages: LLMChatMessage[], separateSystemMessage: string | undefined } => { - - const specialFormat = params.specialToolFormat // this is just for ts stupidness + messages: SimpleLLMMessage[]; + systemMessage: string; + aiInstructions: string; + supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated'; + specialToolFormat: 'openai-style' | 'anthropic-style' | 'gemini-style' | undefined; + supportsAnthropicReasoning: boolean; + contextWindow: number; + reservedOutputTokenSpace: number | null | undefined; + providerName: ProviderName; +}): { messages: LLMChatMessage[]; separateSystemMessage: string | undefined } => { + + const specialFormat = params.specialToolFormat; // this is just for ts stupidness // if need to convert to gemini style of messaes, do that (treat as anthropic style, then convert to gemini style) if (params.providerName === 'gemini' || specialFormat === 'gemini-style') { - const res = prepareOpenAIOrAnthropicMessages({ ...params, specialToolFormat: specialFormat === 'gemini-style' ? 'anthropic-style' : undefined }) - const messages = res.messages as AnthropicLLMChatMessage[] - const messages2 = prepareGeminiMessages(messages) - return { messages: messages2, separateSystemMessage: res.separateSystemMessage } + const res = prepareOpenAIOrAnthropicMessages({ ...params, specialToolFormat: specialFormat === 'gemini-style' ? 'anthropic-style' : undefined, providerName: params.providerName }); + const messages = res.messages as AnthropicLLMChatMessage[]; + const messages2 = prepareGeminiMessages(messages); + return { messages: messages2, separateSystemMessage: res.separateSystemMessage }; } - return prepareOpenAIOrAnthropicMessages({ ...params, specialToolFormat: specialFormat }) -} + return prepareOpenAIOrAnthropicMessages({ ...params, specialToolFormat: specialFormat, providerName: params.providerName }); +}; export interface IConvertToLLMMessageService { readonly _serviceBrand: undefined; - prepareLLMSimpleMessages: (opts: { simpleMessages: SimpleLLMMessage[], systemMessage: string, modelSelection: ModelSelection | null, featureName: FeatureName }) => { messages: LLMChatMessage[], separateSystemMessage: string | undefined } - prepareLLMChatMessages: (opts: { chatMessages: ChatMessage[], chatMode: ChatMode, modelSelection: ModelSelection | null, repoIndexerPromise?: Promise<{ results: string[], metrics: any } | null> }) => Promise<{ messages: LLMChatMessage[], separateSystemMessage: string | undefined }> - prepareFIMMessage(opts: { messages: LLMFIMMessage, }): { prefix: string, suffix: string, stopTokens: string[] } - startRepoIndexerQuery: (chatMessages: ChatMessage[], chatMode: ChatMode) => Promise<{ results: string[], metrics: any } | null> + prepareLLMSimpleMessages: (opts: { simpleMessages: SimpleLLMMessage[]; systemMessage: string; modelSelection: ModelSelection | null; featureName: FeatureName }) => { messages: LLMChatMessage[]; separateSystemMessage: string | undefined }; + prepareLLMChatMessages: (opts: { chatMessages: ChatMessage[]; chatMode: ChatMode; modelSelection: ModelSelection | null; repoIndexerPromise?: Promise<{ results: string[]; metrics: unknown } | null> }) => Promise<{ messages: LLMChatMessage[]; separateSystemMessage: string | undefined }>; + prepareFIMMessage(opts: { messages: LLMFIMMessage }): { prefix: string; suffix: string; stopTokens: string[] }; + startRepoIndexerQuery: (chatMessages: ChatMessage[], chatMode: ChatMode) => Promise<{ results: string[]; metrics: unknown } | null>; } export const IConvertToLLMMessageService = createDecorator('ConvertToLLMMessageService'); @@ -1214,7 +1256,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess @INotificationService private readonly notificationService: INotificationService, @IMemoriesService private readonly memoriesService: IMemoriesService, ) { - super() + super(); } // Read .voidrules files from workspace folders @@ -1223,15 +1265,15 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess const workspaceFolders = this.workspaceContextService.getWorkspace().folders; let voidRules = ''; for (const folder of workspaceFolders) { - const uri = URI.joinPath(folder.uri, '.voidrules') - const { model } = this.cortexideModelService.getModel(uri) - if (!model) continue + const uri = URI.joinPath(folder.uri, '.voidrules'); + const { model } = this.cortexideModelService.getModel(uri); + if (!model) { continue; } voidRules += model.getValue(EndOfLinePreference.LF) + '\n\n'; } return voidRules.trim(); } catch (e) { - return '' + return ''; } } @@ -1240,16 +1282,16 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess const globalAIInstructions = this.cortexideSettingsService.state.globalSettings.aiInstructions; const voidRulesFileContent = this._getVoidRulesFileContents(); - const ans: string[] = [] - if (globalAIInstructions) ans.push(globalAIInstructions) - if (voidRulesFileContent) ans.push(voidRulesFileContent) - return ans.join('\n\n') + const ans: string[] = []; + if (globalAIInstructions) { ans.push(globalAIInstructions); } + if (voidRulesFileContent) { ans.push(voidRulesFileContent); } + return ans.join('\n\n'); } // system message with caching private _generateChatMessagesSystemMessage = async (chatMode: ChatMode, specialToolFormat: 'openai-style' | 'anthropic-style' | 'gemini-style' | undefined) => { - const workspaceFolders = this.workspaceContextService.getWorkspace().folders.map(f => f.uri.fsPath) + const workspaceFolders = this.workspaceContextService.getWorkspace().folders.map(f => f.uri.fsPath); const openedURIs = this.modelService.getModels().filter(m => m.isAttachedToEditor()).map(m => m.uri.fsPath) || []; const activeURI = this.editorService.activeEditor?.resource?.fsPath; @@ -1268,15 +1310,15 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess cutOffMessage: chatMode === 'agent' || chatMode === 'gather' ? `...Directories string cut off, use tools to read more...` : `...Directories string cut off, ask user for more if necessary...` - }) + }); // Always include XML tool definitions in Agent Mode, even if native format is available // This ensures tools are visible to the LLM in both formats - const includeXMLToolDefinitions = !specialToolFormat || chatMode === 'agent' + const includeXMLToolDefinitions = !specialToolFormat || chatMode === 'agent'; - const mcpTools = this.mcpService.getMCPTools() + const mcpTools = this.mcpService.getMCPTools(); - const persistentTerminalIDs = this.terminalToolService.listPersistentTerminalIds() + const persistentTerminalIDs = this.terminalToolService.listPersistentTerminalIds(); // Get relevant memories for the current context (use active file and recent user messages as query) let relevantMemories: string | undefined; @@ -1298,8 +1340,8 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess if (memories.length > 0) { const memoryLines = memories.map(m => { const typeLabel = m.entry.type === 'decision' ? 'Decision' : - m.entry.type === 'preference' ? 'Preference' : - m.entry.type === 'recentFile' ? 'Recent File' : 'Context'; + m.entry.type === 'preference' ? 'Preference' : + m.entry.type === 'recentFile' ? 'Recent File' : 'Context'; return `- [${typeLabel}] ${m.entry.key}: ${m.entry.value}`; }); relevantMemories = memoryLines.join('\n'); @@ -1310,7 +1352,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess } } - const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, persistentTerminalIDs, chatMode, mcpTools, includeXMLToolDefinitions, relevantMemories }) + const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, persistentTerminalIDs, chatMode, mcpTools, includeXMLToolDefinitions, relevantMemories }); // Cache the result this._systemMessageCache.set(cacheKey, { message: systemMessage, timestamp: now }); @@ -1324,8 +1366,8 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess } } - return systemMessage - } + return systemMessage; + }; @@ -1333,17 +1375,17 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess // --- LLM Chat messages --- private _chatMessagesToSimpleMessages(chatMessages: ChatMessage[]): SimpleLLMMessage[] { - const simpleLLMMessages: SimpleLLMMessage[] = [] + const simpleLLMMessages: SimpleLLMMessage[] = []; for (const m of chatMessages) { - if (m.role === 'checkpoint') continue - if (m.role === 'interrupted_streaming_tool') continue + if (m.role === 'checkpoint') { continue; } + if (m.role === 'interrupted_streaming_tool') { continue; } if (m.role === 'assistant') { simpleLLMMessages.push({ role: m.role, content: m.displayContent, anthropicReasoning: m.anthropicReasoning, - }) + }); } else if (m.role === 'tool') { simpleLLMMessages.push({ @@ -1352,36 +1394,36 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess name: m.name, id: m.id, rawParams: m.rawParams, - }) + }); } else if (m.role === 'user') { simpleLLMMessages.push({ role: m.role, content: m.content, images: m.images, - }) + }); } } - return simpleLLMMessages + return simpleLLMMessages; } prepareLLMSimpleMessages: IConvertToLLMMessageService['prepareLLMSimpleMessages'] = ({ simpleMessages, systemMessage, modelSelection, featureName }) => { - if (modelSelection === null) return { messages: [], separateSystemMessage: undefined } + if (modelSelection === null) { return { messages: [], separateSystemMessage: undefined }; } - const { overridesOfModel } = this.cortexideSettingsService.state + const { overridesOfModel } = this.cortexideSettingsService.state; - const { providerName, modelName } = modelSelection + const { providerName, modelName } = modelSelection; // Skip "auto" - it's not a real provider if (providerName === 'auto' && modelName === 'auto') { - throw new Error('Cannot prepare messages for "auto" model selection - must resolve to a real model first') + throw new Error('Cannot prepare messages for "auto" model selection - must resolve to a real model first'); } const { specialToolFormat, contextWindow, supportsSystemMessage, - } = getModelCapabilities(providerName, modelName, overridesOfModel) + } = getModelCapabilities(providerName, modelName, overridesOfModel); - const modelSelectionOptions = this.cortexideSettingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] + const modelSelectionOptions = this.cortexideSettingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]; // Get combined AI instructions const aiInstructions = this._getCombinedAIInstructions(); @@ -1389,8 +1431,8 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess // Keep this method synchronous (indexer enrichment handled in Chat flow) const enrichedSystemMessage = systemMessage; - const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel) - const reservedOutputTokenSpace = getReservedOutputTokenSpace(providerName, modelName, { isReasoningEnabled, overridesOfModel }) + const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel); + const reservedOutputTokenSpace = getReservedOutputTokenSpace(providerName, modelName, { isReasoningEnabled, overridesOfModel }); const { messages, separateSystemMessage } = prepareMessages({ messages: simpleMessages, @@ -1402,9 +1444,9 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess contextWindow, reservedOutputTokenSpace, providerName, - }) + }); return { messages, separateSystemMessage }; - } + }; startRepoIndexerQuery: IConvertToLLMMessageService['startRepoIndexerQuery'] = async (chatMessages, chatMode) => { // PERFORMANCE: Start repo indexer query early (can be done in parallel with router decision) if (!this.cortexideSettingsService.state.globalSettings.enableRepoIndexer) { @@ -1423,38 +1465,38 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess return result; } catch (error) { // Try to warm index if query failed (might not exist yet) - this.repoIndexerService.warmIndex(undefined).catch(() => {}); + this.repoIndexerService.warmIndex(undefined).catch(() => { }); return null; } - } + }; prepareLLMChatMessages: IConvertToLLMMessageService['prepareLLMChatMessages'] = async ({ chatMessages, chatMode, modelSelection, repoIndexerPromise }) => { - if (modelSelection === null) return { messages: [], separateSystemMessage: undefined } + if (modelSelection === null) { return { messages: [], separateSystemMessage: undefined }; } - const { overridesOfModel } = this.cortexideSettingsService.state + const { overridesOfModel } = this.cortexideSettingsService.state; - const { providerName, modelName } = modelSelection + const { providerName, modelName } = modelSelection; // Skip "auto" - it's not a real provider if (providerName === 'auto' && modelName === 'auto') { - throw new Error('Cannot prepare messages for "auto" model selection - must resolve to a real model first') + throw new Error('Cannot prepare messages for "auto" model selection - must resolve to a real model first'); } // At this point, TypeScript knows providerName is not "auto", but we need to assert it for the type system - const validProviderName = providerName as Exclude + const validProviderName = providerName as Exclude; const { specialToolFormat, contextWindow, supportsSystemMessage, - } = getModelCapabilities(validProviderName, modelName, overridesOfModel) + } = getModelCapabilities(validProviderName, modelName, overridesOfModel); const { disableSystemMessage } = this.cortexideSettingsService.state.globalSettings; - const fullSystemMessage = await this._generateChatMessagesSystemMessage(chatMode, specialToolFormat) + const fullSystemMessage = await this._generateChatMessagesSystemMessage(chatMode, specialToolFormat); let systemMessage = disableSystemMessage ? '' : fullSystemMessage; // Query repo indexer if enabled - get context from the LAST user message (most relevant) // PERFORMANCE: Use pre-started promise if available (from parallel execution), otherwise start now if (this.cortexideSettingsService.state.globalSettings.enableRepoIndexer && !disableSystemMessage) { let indexResults: string[] | null = null; - let metrics: any = null; + let metrics: unknown = null; if (repoIndexerPromise) { // Use pre-started query (from parallel execution with router) @@ -1475,7 +1517,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess indexResults = result.results; metrics = result.metrics; } catch (err) { - this.repoIndexerService.warmIndex(undefined).catch(() => {}); + this.repoIndexerService.warmIndex(undefined).catch(() => { }); } } } @@ -1490,7 +1532,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess indexResults = result.results; metrics = result.metrics; } catch (error) { - this.repoIndexerService.warmIndex(undefined).catch(() => {}); + this.repoIndexerService.warmIndex(undefined).catch(() => { }); } } } @@ -1517,48 +1559,48 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess } } else if (!repoIndexerPromise) { // Index might be empty - try to warm it in background (only if we started query ourselves) - this.repoIndexerService.warmIndex(undefined).catch(() => {}); + this.repoIndexerService.warmIndex(undefined).catch(() => { }); } } // Get model options (providerName is already validated above) - const modelSelectionOptions = this.cortexideSettingsService.state.optionsOfModelSelection['Chat'][validProviderName]?.[modelName] + const modelSelectionOptions = this.cortexideSettingsService.state.optionsOfModelSelection['Chat'][validProviderName]?.[modelName]; // Get combined AI instructions const aiInstructions = this._getCombinedAIInstructions(); - const isReasoningEnabled = getIsReasoningEnabledState('Chat', validProviderName, modelName, modelSelectionOptions, overridesOfModel) - const reservedOutputTokenSpace = getReservedOutputTokenSpace(validProviderName, modelName, { isReasoningEnabled, overridesOfModel }) - let llmMessages = this._chatMessagesToSimpleMessages(chatMessages) + const isReasoningEnabled = getIsReasoningEnabledState('Chat', validProviderName, modelName, modelSelectionOptions, overridesOfModel); + const reservedOutputTokenSpace = getReservedOutputTokenSpace(validProviderName, modelName, { isReasoningEnabled, overridesOfModel }); + let llmMessages = this._chatMessagesToSimpleMessages(chatMessages); // Smart context truncation: Prioritize recent messages and user selections - const estimateTokens = (text: string) => Math.ceil(text.length / 4) - const approximateTotalTokens = (msgs: { role: string, content: string }[], sys: string, instr: string) => - msgs.reduce((acc, m) => acc + estimateTokens(m.content), estimateTokens(sys) + estimateTokens(instr)) - const rot = reservedOutputTokenSpace ?? 0 + const estimateTokens = (text: string) => Math.ceil(text.length / 4); + const approximateTotalTokens = (msgs: { role: string; content: string }[], sys: string, instr: string) => + msgs.reduce((acc, m) => acc + estimateTokens(m.content), estimateTokens(sys) + estimateTokens(instr)); + const rot = reservedOutputTokenSpace ?? 0; // More aggressive budget: use 75% instead of 80% to leave more room for output - const budget = Math.max(256, Math.floor(contextWindow * 0.75) - rot) - const beforeTokens = approximateTotalTokens(llmMessages, systemMessage, aiInstructions) + const budget = Math.max(256, Math.floor(contextWindow * 0.75) - rot); + const beforeTokens = approximateTotalTokens(llmMessages, systemMessage, aiInstructions); if (beforeTokens > budget && llmMessages.length > 6) { // Smart truncation: Keep recent messages + prioritize user messages with selections - const keepTailCount = 6 - const head = llmMessages.slice(0, Math.max(0, llmMessages.length - keepTailCount)) - const tail = llmMessages.slice(-keepTailCount) + const keepTailCount = 6; + const head = llmMessages.slice(0, Math.max(0, llmMessages.length - keepTailCount)); + const tail = llmMessages.slice(-keepTailCount); // Prioritize user messages (they contain selections/context) - const userMessages = head.filter(m => m.role === 'user') - const otherMessages = head.filter(m => m.role !== 'user') + const userMessages = head.filter(m => m.role === 'user'); + const otherMessages = head.filter(m => m.role !== 'user'); // Keep more user messages, truncate assistant messages more aggressively - const userSummary = userMessages.map(m => `${m.role}: ${m.content.slice(0, 2000)}`).join('\n').slice(0, 2500) // Reduced from 3000 - const otherSummary = otherMessages.map(m => `${m.role}: ${m.content.slice(0, 500)}`).join('\n').slice(0, 800) // Reduced from 1000 - - const headConcat = userSummary + (otherSummary ? '\n' + otherSummary : '') - const summary = `\n\n\nPrior conversation summarized (${head.length} messages). Key points:\n${headConcat.slice(0, 800)}${headConcat.length > 800 ? '…' : ''}\n` // Reduced from 1000 - systemMessage = (systemMessage || '') + summary - llmMessages = tail - const afterTokens = approximateTotalTokens(llmMessages, systemMessage, aiInstructions) - try { this.notificationService.info(`Context: ~${beforeTokens} → ~${afterTokens} tokens (smart truncation)`)} catch {} + const userSummary = userMessages.map(m => `${m.role}: ${m.content.slice(0, 2000)}`).join('\n').slice(0, 2500); // Reduced from 3000 + const otherSummary = otherMessages.map(m => `${m.role}: ${m.content.slice(0, 500)}`).join('\n').slice(0, 800); // Reduced from 1000 + + const headConcat = userSummary + (otherSummary ? '\n' + otherSummary : ''); + const summary = `\n\n\nPrior conversation summarized (${head.length} messages). Key points:\n${headConcat.slice(0, 800)}${headConcat.length > 800 ? '…' : ''}\n`; // Reduced from 1000 + systemMessage = (systemMessage || '') + summary; + llmMessages = tail; + const afterTokens = approximateTotalTokens(llmMessages, systemMessage, aiInstructions); + try { this.notificationService.info(`Context: ~${beforeTokens} → ~${afterTokens} tokens (smart truncation)`); } catch { } } const { messages, separateSystemMessage } = prepareMessages({ @@ -1571,9 +1613,9 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess contextWindow, reservedOutputTokenSpace, providerName: validProviderName, - }) + }); return { messages, separateSystemMessage }; - } + }; // --- FIM --- @@ -1582,18 +1624,18 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess // Get combined AI instructions with the provided aiInstructions as the base const combinedInstructions = this._getCombinedAIInstructions(); - let prefix = `\ + const prefix = `\ ${!combinedInstructions ? '' : `\ // Instructions: // Do not output an explanation. Try to avoid outputting comments. Only output the middle code. ${combinedInstructions.split('\n').map(line => `//${line}`).join('\n')}`} -${messages.prefix}` +${messages.prefix}`; - const suffix = messages.suffix - const stopTokens = messages.stopTokens - return { prefix, suffix, stopTokens } - } + const suffix = messages.suffix; + const stopTokens = messages.stopTokens; + return { prefix, suffix, stopTokens }; + }; } @@ -1627,6 +1669,7 @@ gemini response: "function_response": { "name": "get_weather", "response": { + // allow-any-unicode-next-line "temperature": "15°C", "condition": "Cloudy" } diff --git a/src/vs/workbench/contrib/cortexide/browser/quickEditActions.ts b/src/vs/workbench/contrib/cortexide/browser/quickEditActions.ts index 53f239e61f0..fd5214d104f 100644 --- a/src/vs/workbench/contrib/cortexide/browser/quickEditActions.ts +++ b/src/vs/workbench/contrib/cortexide/browser/quickEditActions.ts @@ -1,7 +1,7 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Action2, registerAction2, MenuId } from '../../../../platform/actions/common/actions.js'; @@ -26,20 +26,20 @@ import { IFileService } from '../../../../platform/files/common/files.js'; import { joinPath } from '../../../../base/common/resources.js'; export type QuickEditPropsType = { - diffareaid: number, + diffareaid: number; textAreaRef: (ref: HTMLTextAreaElement | null) => void; onChangeHeight: (height: number) => void; onChangeText: (text: string) => void; initText: string | null; -} +}; export type QuickEdit = { - startLine: number, // 0-indexed - beforeCode: string, - afterCode?: string, - instructions?: string, - responseText?: string, // model can produce a text response too -} + startLine: number; // 0-indexed + beforeCode: string; + afterCode?: string; + instructions?: string; + responseText?: string; // model can produce a text response too +}; registerAction2(class extends Action2 { @@ -59,27 +59,27 @@ registerAction2(class extends Action2 { async run(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(ICodeEditorService) - const metricsService = accessor.get(IMetricsService) - metricsService.capture('Ctrl+K', {}) + const editorService = accessor.get(ICodeEditorService); + const metricsService = accessor.get(IMetricsService); + metricsService.capture('Ctrl+K', {}); - const editor = editorService.getActiveCodeEditor() + const editor = editorService.getActiveCodeEditor(); if (!editor) { // If no editor, show a notification that user needs to open a file first - const notificationService = accessor.get(INotificationService) - notificationService.info('Please open a file first to use Quick Edit.') + const notificationService = accessor.get(INotificationService); + notificationService.info('Please open a file first to use Quick Edit.'); return; } - const model = editor.getModel() - if (!model) return; - const selection = roundRangeToLines(editor.getSelection(), { emptySelectionBehavior: 'line' }) - if (!selection) return; + const model = editor.getModel(); + if (!model) { return; } + const selection = roundRangeToLines(editor.getSelection(), { emptySelectionBehavior: 'line' }); + if (!selection) { return; } - const { startLineNumber: startLine, endLineNumber: endLine } = selection + const { startLineNumber: startLine, endLineNumber: endLine } = selection; - const editCodeService = accessor.get(IEditCodeService) - editCodeService.addCtrlKZone({ startLine, endLine, editor }) + const editCodeService = accessor.get(IEditCodeService); + editCodeService.addCtrlKZone({ startLine, endLine, editor }); } }); @@ -106,93 +106,93 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(ICodeEditorService) - const metricsService = accessor.get(IMetricsService) - const quickInputService = accessor.get(IQuickInputService) - const llmMessageService = accessor.get(ILLMMessageService) - const settingsService = accessor.get(ICortexideSettingsService) - const workspaceContextService = accessor.get(IWorkspaceContextService) - const progressService = accessor.get(IProgressService) - const notificationService = accessor.get(INotificationService) - const editCodeService = accessor.get(IEditCodeService) - const fileService = accessor.get(IFileService) - - metricsService.capture('Inline Edit', {}) - - const editor = editorService.getActiveCodeEditor() + const editorService = accessor.get(ICodeEditorService); + const metricsService = accessor.get(IMetricsService); + const quickInputService = accessor.get(IQuickInputService); + const llmMessageService = accessor.get(ILLMMessageService); + const settingsService = accessor.get(ICortexideSettingsService); + const workspaceContextService = accessor.get(IWorkspaceContextService); + const progressService = accessor.get(IProgressService); + const notificationService = accessor.get(INotificationService); + const editCodeService = accessor.get(IEditCodeService); + const fileService = accessor.get(IFileService); + + metricsService.capture('Inline Edit', {}); + + const editor = editorService.getActiveCodeEditor(); if (!editor) { - notificationService.warn('Please open an editor to use Inline Edit.') - return + notificationService.warn('Please open an editor to use Inline Edit.'); + return; } - const model = editor.getModel() + const model = editor.getModel(); if (!model) { - notificationService.warn('No file is open.') - return + notificationService.warn('No file is open.'); + return; } // Check workspace boundary if (!workspaceContextService.isInsideWorkspace(model.uri)) { - notificationService.warn('Inline Edit only works on files within the workspace.') - return + notificationService.warn('Inline Edit only works on files within the workspace.'); + return; } // Get selection or current line - const selection = roundRangeToLines(editor.getSelection(), { emptySelectionBehavior: 'line' }) + const selection = roundRangeToLines(editor.getSelection(), { emptySelectionBehavior: 'line' }); if (!selection) { - notificationService.warn('Please select code or position cursor on a line.') - return + notificationService.warn('Please select code or position cursor on a line.'); + return; } - const { startLineNumber, endLineNumber } = selection - const selectedCode = model.getValueInRange(selection, EndOfLinePreference.LF) + const { startLineNumber, endLineNumber } = selection; + const selectedCode = model.getValueInRange(selection, EndOfLinePreference.LF); // Collect context (nearby lines) - const contextLines = 5 - const contextStart = Math.max(1, startLineNumber - contextLines) - const contextEnd = Math.min(model.getLineCount(), endLineNumber + contextLines) + const contextLines = 5; + const contextStart = Math.max(1, startLineNumber - contextLines); + const contextEnd = Math.min(model.getLineCount(), endLineNumber + contextLines); const contextCode = model.getValueInRange( { startLineNumber: contextStart, startColumn: 1, endLineNumber: contextEnd, endColumn: Number.MAX_SAFE_INTEGER }, EndOfLinePreference.LF - ) + ); // Prompt for edit instruction const instruction = await quickInputService.input({ placeHolder: localize2('voidInlineEditPlaceholder', 'Describe what you want to change (e.g., "add error handling", "refactor to use async/await")...').value, prompt: localize2('voidInlineEditPrompt', 'Edit instruction').value, - }).then((result: string | undefined) => result) + }).then((result: string | undefined) => result); - if (!instruction) return + if (!instruction) { return; } // Check for model selection - const modelSelection = settingsService.state.modelSelectionOfFeature['Chat'] + const modelSelection = settingsService.state.modelSelectionOfFeature['Chat']; if (!modelSelection) { - notificationService.warn('Please select a model in CortexIDE Settings to use Inline Edit.') - return + notificationService.warn('Please select a model in CortexIDE Settings to use Inline Edit.'); + return; } // Generate edit using LLM - let generatedEdit: string | null = null - let requestId: string | null = null - let isComplete = false - let errorMessage: string | null = null - const cancellationToken = new CancellationTokenSource() + let generatedEdit: string | null = null; + let requestId: string | null = null; + let isComplete = false; + let errorMessage: string | null = null; + const cancellationToken = new CancellationTokenSource(); // Read project Rules files if they exist - let projectRules = '' + let projectRules = ''; try { - const workspaceFolder = workspaceContextService.getWorkspaceFolder(model.uri) + const workspaceFolder = workspaceContextService.getWorkspaceFolder(model.uri); if (workspaceFolder) { - const rulesFiles = ['.cursorrules', '.voidrules', '.rules'] + const rulesFiles = ['.cursorrules', '.voidrules', '.rules']; for (const rulesFile of rulesFiles) { - const rulesUri = joinPath(workspaceFolder.uri, rulesFile) + const rulesUri = joinPath(workspaceFolder.uri, rulesFile); try { - const content = await fileService.readFile(rulesUri) + const content = await fileService.readFile(rulesUri); if (content && content.value) { - const rulesText = content.value.toString().trim() + const rulesText = content.value.toString().trim(); if (rulesText) { - projectRules = `\n\nProject Rules (from ${rulesFile}):\n${rulesText}\n` - break // Use first found rules file + projectRules = `\n\nProject Rules (from ${rulesFile}):\n${rulesText}\n`; + break; // Use first found rules file } } } catch { @@ -228,16 +228,16 @@ ${selectedCode} Context (nearby lines): \`\`\` ${contextCode} -\`\`\`` +\`\`\``; - const userMessage = `Edit instruction: ${instruction}\n\nGenerate a SEARCH/REPLACE block for the selected code.` + const userMessage = `Edit instruction: ${instruction}\n\nGenerate a SEARCH/REPLACE block for the selected code.`; - const chatOptions = settingsService.state.optionsOfModelSelection['Chat'] + const chatOptions = settingsService.state.optionsOfModelSelection['Chat']; // Skip "auto" - it's not a real provider const modelOptions = modelSelection && !(modelSelection.providerName === 'auto' && modelSelection.modelName === 'auto') ? chatOptions[modelSelection.providerName]?.[modelSelection.modelName] - : undefined - const overrides = settingsService.state.overridesOfModel + : undefined; + const overrides = settingsService.state.overridesOfModel; requestId = llmMessageService.sendLLMMessage({ messagesType: 'chatMessages', @@ -252,70 +252,78 @@ ${contextCode} separateSystemMessage: systemMessage, logging: { loggingName: 'Inline Edit', loggingExtras: {} }, onText: ({ fullText }) => { - generatedEdit = fullText - progress.report({ message: 'Generating...' }) + generatedEdit = fullText; + progress.report({ message: 'Generating...' }); }, onFinalMessage: ({ fullText }) => { - generatedEdit = fullText - isComplete = true + generatedEdit = fullText; + isComplete = true; }, onError: ({ message }) => { - errorMessage = message - isComplete = true + errorMessage = message; + isComplete = true; }, onAbort: () => { - isComplete = true + isComplete = true; }, - }) + }); if (!requestId) { - throw new Error('Failed to start LLM request') + throw new Error('Failed to start LLM request'); } // Wait for completion with cancellation support await new Promise((resolve) => { + let resolved = false; + const doResolve = () => { + if (resolved) { return; } + resolved = true; + resolve(); + }; + const timeout = setTimeout(() => { if (requestId && !isComplete) { - llmMessageService.abort(requestId) - errorMessage = 'Timeout after 30 seconds' - isComplete = true + llmMessageService.abort(requestId); + errorMessage = 'Timeout after 30 seconds'; + isComplete = true; } - resolve() - }, 30000) // 30s timeout + clearInterval(checkInterval); + doResolve(); + }, 30000); // 30s timeout const checkInterval = setInterval(() => { if (cancellationToken.token.isCancellationRequested) { - clearInterval(checkInterval) - clearTimeout(timeout) + clearInterval(checkInterval); + clearTimeout(timeout); if (requestId && !isComplete) { - llmMessageService.abort(requestId) + llmMessageService.abort(requestId); } - isComplete = true - resolve() - return + isComplete = true; + doResolve(); + return; } if (isComplete) { - clearInterval(checkInterval) - clearTimeout(timeout) - resolve() + clearInterval(checkInterval); + clearTimeout(timeout); + doResolve(); } - }, 100) - }) + }, 100); + }); }, () => { // onDidCancel callback - cancellationToken.cancel() + cancellationToken.cancel(); } - ) + ); if (errorMessage) { - notificationService.error(`Inline Edit failed: ${errorMessage}`) - return + notificationService.error(`Inline Edit failed: ${errorMessage}`); + return; } if (!generatedEdit || typeof generatedEdit !== 'string') { - notificationService.warn('No edit was generated. Please try again.') - return + notificationService.warn('No edit was generated. Please try again.'); + return; } // Extract search/replace blocks from response @@ -323,21 +331,26 @@ ${contextCode} const searchReplaceBlocks = (generatedEdit as string) .replace(/```[\w]*\n?/g, '') .replace(/```/g, '') - .trim() + .trim(); if (!searchReplaceBlocks.includes('<<<<<<< ORIGINAL') || !searchReplaceBlocks.includes('>>>>>>> UPDATED')) { - notificationService.warn('The model did not generate a valid SEARCH/REPLACE block. Please try again or use Ctrl+K Quick Edit.') - return + notificationService.warn('The model did not generate a valid SEARCH/REPLACE block. Please try again or use Ctrl+K Quick Edit.'); + return; } // Apply the edit using existing EditCodeService // This will show the diff preview automatically - await editCodeService.callBeforeApplyOrEdit(model.uri) - editCodeService.instantlyApplySearchReplaceBlocks({ - uri: model.uri, - searchReplaceBlocks, - }) - - notificationService.info('Edit generated. Review the diff and apply or reject changes.') + try { + await editCodeService.callBeforeApplyOrEdit(model.uri); + editCodeService.instantlyApplySearchReplaceBlocks({ + uri: model.uri, + searchReplaceBlocks, + }); + notificationService.info('Edit generated. Review the diff and apply or reject changes.'); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + notificationService.error(`Failed to apply edit: ${errorMessage}`); + return; + } } -}) +}); diff --git a/src/vs/workbench/contrib/cortexide/browser/react/src/quick-edit-tsx/QuickEditChat.tsx b/src/vs/workbench/contrib/cortexide/browser/react/src/quick-edit-tsx/QuickEditChat.tsx index e43e9d83387..171db3581b0 100644 --- a/src/vs/workbench/contrib/cortexide/browser/react/src/quick-edit-tsx/QuickEditChat.tsx +++ b/src/vs/workbench/contrib/cortexide/browser/react/src/quick-edit-tsx/QuickEditChat.tsx @@ -1,7 +1,7 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useSettingsState, useAccessor, useCtrlKZoneStreamingState } from '../util/services.js'; @@ -35,7 +35,23 @@ export const QuickEditChat = ({ // only observing 1 element let resizeObserver: ResizeObserver | undefined resizeObserver = new ResizeObserver((entries) => { - const height = entries[0].borderBoxSize[0].blockSize + if (!entries[0]) return; + + // borderBoxSize might not be available in all browsers or might be undefined + // Fall back to contentRect if borderBoxSize is not available + let height: number; + + if (entries[0].borderBoxSize && entries[0].borderBoxSize.length > 0) { + height = entries[0].borderBoxSize[0].blockSize; + } else if (entries[0].contentRect) { + // Fallback to contentRect for older browsers + height = entries[0].contentRect.height; + } else { + // Last resort: use target's client dimensions + const target = entries[0].target as HTMLElement; + height = target.clientHeight; + } + onChangeHeight(height) }) resizeObserver.observe(inputContainer); diff --git a/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/ChatTabsBar.tsx b/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/ChatTabsBar.tsx new file mode 100644 index 00000000000..c9003623f41 --- /dev/null +++ b/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/ChatTabsBar.tsx @@ -0,0 +1,149 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import React, { useMemo, useState } from 'react'; +import { useAccessor, useChatThreadsState, useFullChatThreadsStreamState } from '../util/services.js'; +import { IconX } from './SidebarChat.js'; +import { X, LoaderCircle, MessageCircleQuestion } from 'lucide-react'; +import { IsRunningType, ThreadType } from '../../../chatThreadService.js'; +import { IconShell1 } from '../markdown/ApplyBlockHoverButtons.js'; + +// Get a short title for a thread (first user message or "New Chat") +const getThreadTitle = (thread: ThreadType | undefined): string => { + if (!thread) return 'New Chat'; + const firstUserMsgIdx = thread.messages.findIndex((msg) => msg.role === 'user'); + if (firstUserMsgIdx !== -1) { + const firstUserMsg = thread.messages[firstUserMsgIdx]; + if (firstUserMsg.role === 'user' && firstUserMsg.displayContent) { + // Truncate to 30 characters + const title = firstUserMsg.displayContent; + return title.length > 30 ? title.substring(0, 30) + '...' : title; + } + } + return 'New Chat'; +}; + +interface ChatTabProps { + threadId: string; + isActive: boolean; + onClick: () => void; + onClose: (e: React.MouseEvent) => void; + isRunning?: IsRunningType; +} + +const ChatTab: React.FC = ({ threadId, isActive, onClick, onClose, isRunning }) => { + const threadsState = useChatThreadsState(); + const thread = threadsState.allThreads[threadId]; + const title = getThreadTitle(thread); + + return ( +
+ {/* Status icon */} + {isRunning === 'LLM' || isRunning === 'tool' || isRunning === 'preparing' ? ( + + ) : isRunning === 'awaiting_user' ? ( + + ) : null} + + {/* Title */} + + {title} + + + {/* Close button */} + { + e.stopPropagation(); + onClose(e); + }} + data-tooltip-id='void-tooltip' + data-tooltip-place='top' + data-tooltip-content='Close tab' + /> +
+ ); +}; + +export const ChatTabsBar: React.FC = () => { + const accessor = useAccessor(); + const chatThreadsService = accessor.get('IChatThreadService'); + const threadsState = useChatThreadsState(); + const streamState = useFullChatThreadsStreamState(); + + const { openTabs, currentThreadId } = threadsState; + + // Memoize running thread IDs + const runningThreadIds = useMemo(() => { + const result: { [threadId: string]: IsRunningType | undefined } = {}; + for (const threadId in streamState) { + const isRunning = streamState[threadId]?.isRunning; + if (isRunning) { + result[threadId] = isRunning; + } + } + return result; + }, [streamState]); + + // Filter out tabs that no longer exist + const validTabs = useMemo(() => { + return openTabs.filter(threadId => threadsState.allThreads[threadId] !== undefined); + }, [openTabs, threadsState.allThreads]); + + // If no tabs, don't show the bar + if (validTabs.length === 0) { + return null; + } + + const handleTabClick = (threadId: string) => { + chatThreadsService.switchToTab(threadId); + }; + + const handleTabClose = (threadId: string) => { + chatThreadsService.closeTab(threadId); + }; + + const handleNewTab = () => { + chatThreadsService.openNewThread(); + }; + + return ( +
+
+ {validTabs.map((threadId) => ( + handleTabClick(threadId)} + onClose={() => handleTabClose(threadId)} + isRunning={runningThreadIds[threadId]} + /> + ))} +
+ {/* New tab button */} + +
+ ); +}; + diff --git a/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarChat.tsx index d3d1b4c1101..28b42a54b8f 100644 --- a/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -1,7 +1,7 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -16,6 +16,7 @@ import { ErrorDisplay } from './ErrorDisplay.js'; import { BlockCode, TextAreaFns, VoidCustomDropdownBox, VoidInputBox2, VoidSlider, VoidSwitch, VoidDiffEditor } from '../util/inputs.js'; import { ModelDropdown, } from '../void-settings-tsx/ModelDropdown.js'; import { PastThreadsList } from './SidebarThreadSelector.js'; +import { ChatTabsBar } from './ChatTabsBar.js'; import { CORTEXIDE_CTRL_L_ACTION_ID } from '../../../actionIDs.js'; import { CORTEXIDE_OPEN_SETTINGS_ACTION_ID } from '../../../cortexideSettingsPane.js'; import { ChatMode, displayInfoOfProviderName, FeatureName, isFeatureNameDisabled, isValidProviderModelSelection } from '../../../../../../../workbench/contrib/cortexide/common/cortexideSettingsTypes.js'; @@ -501,15 +502,15 @@ export const VoidChatArea: React.FC = ({ }} className={` gap-x-1 - flex flex-col p-2.5 relative input text-left shrink-0 - rounded-2xl - bg-[#030304] + flex flex-col p-2.5 relative input text-left shrink-0 + rounded-2xl + bg-[#030304] transition-all duration-200 border border-[rgba(255,255,255,0.08)] focus-within:border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.12)] ${isDragOver ? 'border-blue-500 bg-blue-500/10' : ''} max-h-[80vh] overflow-y-auto - ${className} - `} + ${className} + `} onClick={(e) => { onClickAnywhere?.() }} @@ -1104,7 +1105,7 @@ const ToolHeaderWrapper = ({ {
{children} @@ -1422,24 +1423,24 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCheckpointGhost, curr return
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} >
{ if (mode === 'display') { onOpenEdit() } }} > {chatbubbleContents} @@ -1456,12 +1457,12 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCheckpointGhost, curr { if (mode === 'display') { onOpenEdit() @@ -1871,13 +1872,13 @@ const ToolRequestAcceptRejectButtons = ({ toolName }: { toolName: ToolName }) => @@ -1887,13 +1888,13 @@ const ToolRequestAcceptRejectButtons = ({ toolName }: { toolName: ToolName }) => @@ -2077,9 +2078,29 @@ const CommandTool = ({ toolMessage, type, threadId }: { threadId: string } & ({ // Listen for size changes of the container and keep the terminal layout in sync. const resizeObserver = new ResizeObserver((entries) => { - const height = entries[0].borderBoxSize[0].blockSize; - const width = entries[0].borderBoxSize[0].inlineSize; - if (typeof terminal.layout === 'function') { + if (!entries[0]) return; + + // borderBoxSize might not be available in all browsers or might be undefined + // Fall back to contentRect if borderBoxSize is not available + let width: number; + let height: number; + + if (entries[0].borderBoxSize && entries[0].borderBoxSize.length > 0) { + width = entries[0].borderBoxSize[0].inlineSize; + height = entries[0].borderBoxSize[0].blockSize; + } else if (entries[0].contentRect) { + // Fallback to contentRect for older browsers + width = entries[0].contentRect.width; + height = entries[0].contentRect.height; + } else { + // Last resort: use target's client dimensions + const target = entries[0].target as HTMLElement; + width = target.clientWidth; + height = target.clientHeight; + } + + // Only layout if we have valid dimensions + if (width > 0 && height > 0 && typeof terminal.layout === 'function') { terminal.layout({ width, height }); } }); @@ -2926,12 +2947,12 @@ const Checkpoint = ({ message, threadId, messageIdx, isCheckpointGhost, threadIs >
{ if (threadIsRunning) return @@ -2974,10 +2995,10 @@ const PlanComponent = React.memo(({ message, isCheckpointGhost, threadId, messag // Subscribe to thread state changes properly const chatThreadsState = useChatThreadsState() - const approvalState = message.approvalState || 'pending' - const isRunning = useChatThreadsStreamState(threadId)?.isRunning - const isBusy = isRunning === 'LLM' || isRunning === 'tool' || isRunning === 'preparing' - const isIdleLike = isRunning === undefined || isRunning === 'idle' + const approvalState = message.approvalState || 'pending' + const isRunning = useChatThreadsStreamState(threadId)?.isRunning + const isBusy = isRunning === 'LLM' || isRunning === 'tool' || isRunning === 'preparing' + const isIdleLike = isRunning === undefined || isRunning === 'idle' // Get thread messages with proper subscription const thread = chatThreadsState.allThreads[threadId] @@ -3066,18 +3087,18 @@ const PlanComponent = React.memo(({ message, isCheckpointGhost, threadId, messag }) } - const handleApprove = () => { - if (isCheckpointGhost || isBusy) return + const handleApprove = () => { + if (isCheckpointGhost || isBusy) return chatThreadService.approvePlan({ threadId, messageIdx }) } const handleReject = () => { - if (isCheckpointGhost || isBusy) return + if (isCheckpointGhost || isBusy) return chatThreadService.rejectPlan({ threadId, messageIdx }) } const handleToggleStep = (stepNumber: number) => { - if (isCheckpointGhost || isBusy) return + if (isCheckpointGhost || isBusy) return chatThreadService.toggleStepDisabled({ threadId, messageIdx, stepNumber }) } @@ -3138,7 +3159,7 @@ const PlanComponent = React.memo(({ message, isCheckpointGhost, threadId, messag {!isCollapsed && (
{progressText} - {approvalState === 'pending' && isIdleLike && ( + {approvalState === 'pending' && isIdleLike && (
- const keybindingService = accessor.get('IKeybindingService') - const quickActions: { id: string, label: string }[] = [ - { id: 'void.explainCode', label: 'Explain' }, - { id: 'void.refactorCode', label: 'Refactor' }, - { id: 'void.addTests', label: 'Add Tests' }, - { id: 'void.fixTests', label: 'Fix Tests' }, - { id: 'void.writeDocstring', label: 'Docstring' }, - { id: 'void.optimizeCode', label: 'Optimize' }, - { id: 'void.debugCode', label: 'Debug' }, - ] - - const QuickActionsBar = () => ( -
- {quickActions.map(({ id, label }) => { - const kb = keybindingService.lookupKeybinding(id)?.getLabel() - return ( - - ) - })} -
- ) - - // Lightweight context chips: active file and model - const ContextChipsBar = () => { - const editorService = accessor.get('IEditorService') - const activeEditor = editorService?.activeEditor - // Try best-effort file label - const activeResource = activeEditor?.resource - const activeFileLabel = activeResource ? activeResource.path?.split('/').pop() : undefined - const modelSel = settingsState.modelSelectionOfFeature['Chat'] - const modelLabel = modelSel ? `${modelSel.providerName}:${modelSel.modelName}` : undefined - if (!activeFileLabel && !modelLabel) return null - return ( -
- {activeFileLabel && ( - - File - {activeFileLabel} - - )} - {modelLabel && ( - - Model - {modelLabel} - - )} -
- ) - } - - const landingPageContent =
( +
+ {quickActions.map(({ id, label }) => { + const kb = keybindingService.lookupKeybinding(id)?.getLabel() + return ( + + ) + })} +
+ ) + + // Lightweight context chips: active file and model + const ContextChipsBar = () => { + const editorService = accessor.get('IEditorService') + const activeEditor = editorService?.activeEditor + // Try best-effort file label + const activeResource = activeEditor?.resource + const activeFileLabel = activeResource ? activeResource.path?.split('/').pop() : undefined + const modelSel = settingsState.modelSelectionOfFeature['Chat'] + const modelLabel = modelSel ? `${modelSel.providerName}:${modelSel.modelName}` : undefined + if (!activeFileLabel && !modelLabel) return null + return ( +
+ {activeFileLabel && ( + + File + {activeFileLabel} + + )} + {modelLabel && ( + + Model + {modelLabel} + + )} +
+ ) + } + + const landingPageContent =
- {landingPageInput} + +
+ + {landingPageInput} + {/* Context chips */} - {/* Quick Actions shortcuts */} - - - + {/* Quick Actions shortcuts */} + + + {Object.keys(chatThreadsState.allThreads).length > 1 ? // show if there are threads @@ -4785,6 +4810,7 @@ export const SidebarChat = () => { {initiallySuggestedPromptsHTML} } +
@@ -4805,7 +4831,9 @@ export const SidebarChat = () => { ref={sidebarRef} className='w-full h-full flex flex-col overflow-hidden' > - + + + {messagesHTML} diff --git a/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx b/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx index 6925de4243e..e3674f361fe 100644 --- a/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx +++ b/src/vs/workbench/contrib/cortexide/browser/react/src/sidebar-tsx/SidebarThreadSelector.tsx @@ -1,7 +1,7 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import { useMemo, useState } from 'react'; import { CopyButton, IconShell1 } from '../markdown/ApplyBlockHoverButtons.js'; @@ -238,19 +238,19 @@ const PastThreadElement = ({ pastThread, idx, hoveredIdx, setHoveredIdx, isRunni group px-3 py-2 rounded-xl border border-void-border-3/70 bg-void-bg-1/40 hover:bg-void-bg-2/70 cursor-pointer text-sm text-void-fg-1 transition-all duration-150 ease-out shadow-[0_8px_20px_rgba(0,0,0,0.35)] hover:-translate-y-0.5 `} onClick={() => { - chatThreadsService.switchToThread(pastThread.id); + chatThreadsService.openTab(pastThread.id); }} onMouseEnter={() => setHoveredIdx(idx)} onMouseLeave={() => setHoveredIdx(null)} >
- {/* status icon */} - {isRunning === 'LLM' || isRunning === 'tool' || isRunning === 'preparing' ? ( - - ) : isRunning === 'awaiting_user' ? ( - - ) : null} + {/* status icon */} + {isRunning === 'LLM' || isRunning === 'tool' || isRunning === 'preparing' ? ( + + ) : isRunning === 'awaiting_user' ? ( + + ) : null} {/* name */} { ) } -const VoidIcon = () => ( -
- CortexIDE logo -
-) +const VoidIcon = () => { + const [imageError, setImageError] = useState(false); + const [imageSrc, setImageSrc] = useState(() => { + try { + return FileAccess.asBrowserUri('vs/workbench/browser/media/cortexide-main.png').toString(true); + } catch (e) { + // Fallback: try without toString(true) + try { + return FileAccess.asBrowserUri('vs/workbench/browser/media/cortexide-main.png').toString(); + } catch (e2) { + return ''; + } + } + }); + const [retryCount, setRetryCount] = useState(0); + + const handleImageError = () => { + if (retryCount === 0) { + // First retry: try without toString(true) + setRetryCount(1); + try { + const altUri = FileAccess.asBrowserUri('vs/workbench/browser/media/cortexide-main.png').toString(); + setImageSrc(altUri); + } catch (e) { + setImageError(true); + } + } else { + // Second failure: show placeholder + setImageError(true); + } + }; + + return ( +
+ {imageError || !imageSrc ? ( +
+
C
+
+ ) : ( + CortexIDE logo { + setImageError(false); + }} + loading="eager" + /> + )} +
+ ); +} const FADE_DURATION_MS = 2000 @@ -418,6 +462,7 @@ const WelcomePage = ({ onNext, onSkip }: { onNext: () => void; onSkip: () => voi

Build with the editor AI actually ships in

+ {/* allow-any-unicode-next-line */} CortexIDE keeps Chat, Quick Edit, Fast Apply, and source control in the same dark workspace—and it adds native PDF + image uploads so product specs and design mocks travel with every conversation.

@@ -429,8 +474,27 @@ const WelcomePage = ({ onNext, onSkip }: { onNext: () => void; onSkip: () => voi ))}
- Start guided setup - Skip for now + { + e.preventDefault(); + e.stopPropagation(); + onNext(); + }} + className="cursor-pointer active:scale-[0.98]" + > + Start guided setup + + { + e.preventDefault(); + e.stopPropagation(); + onSkip(); + }} + className="cursor-pointer active:scale-[0.98]" + > + Skip for now +
@@ -525,16 +589,13 @@ const PrimaryActionButton = ({ children, className = '', ringSize, ...props }: { hover:shadow-[0_45px_100px_rgba(0,0,0,0.7)] hover:translate-y-[-1px] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-white/20 focus-visible:ring-offset-[#050612] - transition-all duration-300 group + transition-all duration-300 ${sizingClass} ${className} `} {...props} > {children} - ) } @@ -637,14 +698,28 @@ const VoidOnboardingContent = () => { { setPageIndex(pageIndex - 1) }} /> - skipOnboarding('final-step-skip')}>Skip for now + { + e.preventDefault(); + e.stopPropagation(); + skipOnboarding('final-step-skip'); + }} + className="cursor-pointer active:scale-[0.98]" + > + Skip for now + { + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); cortexideSettingsService.setGlobalSetting('isOnboardingComplete', true); voidMetricsService.capture('Completed Onboarding', { selectedProviderName, wantToUseOption }) }} ringSize={voidSettingsState.globalSettings.isOnboardingComplete ? 'screen' : undefined} - >Start with CortexIDE + className="cursor-pointer active:scale-[0.98]" + > + Start with CortexIDE +
diff --git a/src/vs/workbench/contrib/cortexide/browser/repoIndexerService.ts b/src/vs/workbench/contrib/cortexide/browser/repoIndexerService.ts index 320fb4b3be6..5c280ecc655 100644 --- a/src/vs/workbench/contrib/cortexide/browser/repoIndexerService.ts +++ b/src/vs/workbench/contrib/cortexide/browser/repoIndexerService.ts @@ -1,3 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; @@ -241,32 +246,32 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { private _getIndexPath(): URI | null { const workspace = this.workspaceContextService.getWorkspace(); - if (!workspace.id) return null; - // Store index outside workspace in workspaceStorageHome, similar to how Cursor does it - // This keeps the workspace clean and prevents index files from being visible to users - return joinPath(this.environmentService.workspaceStorageHome, workspace.id, 'codebase-index.json'); + const workspaceFolder = workspace.folders[0]?.uri; + if (!workspaceFolder) { return null; } + // Store index in .cortexide directory within the workspace + return joinPath(workspaceFolder, '.cortexide', 'index.json'); } private async _loadIndex(): Promise { const indexPath = this._getIndexPath(); - if (!indexPath) return; + if (!indexPath) { return; } try { const content = await this.fileService.readFile(indexPath); const data = JSON.parse(content.value.toString()); if (Array.isArray(data)) { // Validate and deserialize index entries (convert arrays back to Sets) - this._index = data.filter((entry: any) => + this._index = data.filter((entry: unknown) => entry && typeof entry.uri === 'string' && Array.isArray(entry.symbols) && typeof entry.snippet === 'string' - ).map((entry: any) => ({ + ).map((entry: unknown) => ({ ...entry, // Convert arrays back to Sets for fast lookups snippetTokens: entry.snippetTokens ? new Set(entry.snippetTokens) : undefined, uriTokens: entry.uriTokens ? new Set(entry.uriTokens) : undefined, symbolTokens: entry.symbolTokens ? new Set(entry.symbolTokens) : undefined, - chunks: entry.chunks?.map((chunk: any) => ({ + chunks: entry.chunks?.map((chunk: unknown) => ({ ...chunk, tokens: chunk.tokens ? new Set(chunk.tokens) : undefined, embedding: chunk.embedding && Array.isArray(chunk.embedding) ? chunk.embedding : undefined @@ -283,32 +288,34 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { this._isWarmed = true; } } catch (error) { - // Try to migrate from old location (.cortexide/index.json in workspace) + // Try to migrate from old location (workspaceStorageHome) await this._tryMigrateFromOldLocation(); } } private async _tryMigrateFromOldLocation(): Promise { - const workspace = this.workspaceContextService.getWorkspace().folders[0]?.uri; - if (!workspace) return; + const workspace = this.workspaceContextService.getWorkspace(); + const workspaceFolder = workspace.folders[0]?.uri; + if (!workspaceFolder || !workspace.id) { return; } - const oldIndexPath = workspace.with({ path: `${workspace.path}/.cortexide/index.json` }); + // Try to migrate from workspaceStorageHome (old location) + const oldIndexPath = joinPath(this.environmentService.workspaceStorageHome, workspace.id, 'codebase-index.json'); try { const content = await this.fileService.readFile(oldIndexPath); const data = JSON.parse(content.value.toString()); if (Array.isArray(data)) { // Validate and migrate index entries (convert arrays to Sets if present) - this._index = data.filter((entry: any) => + this._index = data.filter((entry: unknown) => entry && typeof entry.uri === 'string' && Array.isArray(entry.symbols) && typeof entry.snippet === 'string' - ).map((entry: any) => ({ + ).map((entry: unknown) => ({ ...entry, snippetTokens: entry.snippetTokens ? (Array.isArray(entry.snippetTokens) ? new Set(entry.snippetTokens) : entry.snippetTokens) : undefined, uriTokens: entry.uriTokens ? (Array.isArray(entry.uriTokens) ? new Set(entry.uriTokens) : entry.uriTokens) : undefined, symbolTokens: entry.symbolTokens ? (Array.isArray(entry.symbolTokens) ? new Set(entry.symbolTokens) : entry.symbolTokens) : undefined, - chunks: entry.chunks?.map((chunk: any) => ({ + chunks: entry.chunks?.map((chunk: unknown) => ({ ...chunk, tokens: chunk.tokens ? (Array.isArray(chunk.tokens) ? new Set(chunk.tokens) : chunk.tokens) : undefined })), @@ -319,9 +326,9 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { // Rebuild inverted indexes from migrated data this._rebuildInvertedIndexes(); this._isWarmed = true; - // Save to new location + // Save to new location (.cortexide/index.json) await this._saveIndex(); - console.debug('[RepoIndexer] Migrated index from old location to new location'); + console.debug('[RepoIndexer] Migrated index from workspaceStorageHome to .cortexide/index.json'); } } catch (error) { // Old index doesn't exist or is invalid, will rebuild on demand @@ -331,19 +338,19 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { private async _saveIndex(): Promise { const indexPath = this._getIndexPath(); - if (!indexPath) return; + if (!indexPath) { return; } try { // Serialize with Set conversion for JSON compatibility // Optimize: remove undefined/null fields and use compact format const serializableIndex = this._index.map(entry => { - const result: any = { + const result: unknown = { uri: entry.uri, symbols: entry.symbols, snippet: entry.snippet }; // Only include optional fields if they exist - if (entry.snippetStartLine) result.snippetStartLine = entry.snippetStartLine; - if (entry.snippetEndLine) result.snippetEndLine = entry.snippetEndLine; + if (entry.snippetStartLine) { result.snippetStartLine = entry.snippetStartLine; } + if (entry.snippetEndLine) { result.snippetEndLine = entry.snippetEndLine; } if (entry.snippetTokens && entry.snippetTokens.size > 0) { result.snippetTokens = Array.from(entry.snippetTokens); } @@ -355,7 +362,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { } if (entry.chunks && entry.chunks.length > 0) { result.chunks = entry.chunks.map(chunk => { - const chunkResult: any = { + const chunkResult: unknown = { text: chunk.text, startLine: chunk.startLine, endLine: chunk.endLine @@ -415,10 +422,10 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { const walk = async (dir: URI): Promise => { try { const entries = await this.fileService.resolve(dir); - if (!entries.children) return; + if (!entries.children) { return; } for (const child of entries.children) { - if (shouldIgnore(child.resource.path)) continue; + if (shouldIgnore(child.resource.path)) { continue; } if (child.isDirectory) { await walk(child.resource); @@ -451,7 +458,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { const astSymbols = await this.treeSitterService.extractSymbols(uri, fileContent); // Convert AST symbols to string array - const extractNames = (sym: any): void => { + const extractNames = (sym: unknown): void => { if (sym.name && !symbols.includes(sym.name)) { symbols.push(sym.name); } @@ -479,21 +486,21 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { // Only extract symbols if model is already loaded (to avoid expensive model initialization) // During rebuild, symbols will only be extracted for files that are already open/loaded const model = this.modelService.getModel(uri); - if (!model) return symbols; + if (!model) { return symbols; } const docSymbols = await this.languageFeaturesService.documentSymbolProvider.ordered(model); - if (!docSymbols || docSymbols.length === 0) return symbols; + if (!docSymbols || docSymbols.length === 0) { return symbols; } for (const provider of docSymbols) { - const docSymbols_ = await provider.provideDocumentSymbols(model, {} as any); + const docSymbols_ = await provider.provideDocumentSymbols(model, {} as unknown); if (docSymbols_) { - const extract = (sym: any): void => { + const extract = (sym: unknown): void => { const name = sym.name || ''; if (name && !symbols.includes(name)) { symbols.push(name); } if (sym.children) { - for (const child of sym.children) extract(child); + for (const child of sym.children) { extract(child); } } }; for (const sym of docSymbols_) { @@ -673,7 +680,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { currentPos += chunkSize - chunkOverlap; chunkIndex++; - if (chunkEndPos >= text.length) break; + if (chunkEndPos >= text.length) { break; } } } @@ -828,7 +835,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { } async warmIndex(workspaceRoot?: URI): Promise { - if (this._isWarmed) return; + if (this._isWarmed) { return; } const workspace = this.workspaceContextService.getWorkspace(); if (!workspace.id) { @@ -925,7 +932,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { // If index is empty, try to warm it first (non-blocking) if (this._index.length === 0 && !this._isWarmed) { // Trigger background warmup without waiting - this.warmIndex(undefined).catch(() => {}); + this.warmIndex(undefined).catch(() => { }); } // First, try the on-disk index @@ -1048,13 +1055,13 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { const entryIndex = entriesToScore[i]; const entry = this._index[entryIndex]; - if (!entry) continue; + if (!entry) { continue; } // Score main snippet (using pre-computed tokens for faster matching) const mainScore = this._scoreEntryFast(q, qTokens, entry); if (mainScore > 0) { scoredItems.push({ entry, score: mainScore, isChunk: false }); - if (mainScore >= 5) highScoreCount++; + if (mainScore >= 5) { highScoreCount++; } } // Lazy chunk evaluation: only score chunks if main snippet score is promising @@ -1066,7 +1073,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { const chunkScore = this._scoreChunkFast(q, qTokens, chunk); if (chunkScore > 0) { scoredItems.push({ entry, chunk, score: chunkScore, isChunk: true }); - if (chunkScore >= 5) highScoreCount++; + if (chunkScore >= 5) { highScoreCount++; } } } } @@ -1360,8 +1367,8 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { * Optimized BM25 reranking with pre-computed tokens and cached term frequencies */ private _rerankBM25Fast(query: string, qTokens: Set, items: Array<{ entry: IndexEntry; chunk?: IndexChunk; score: number; isChunk: boolean }>, k: number): typeof items { - if (items.length === 0) return items; - if (qTokens.size === 0) return items; + if (items.length === 0) { return items; } + if (qTokens.size === 0) { return items; } // Use cached average document length (much faster than computing per query) const avgDocLength = this._getAvgDocLength() || items.reduce((sum, item) => { @@ -1465,7 +1472,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { items: Array<{ entry: IndexEntry; chunk?: IndexChunk; score: number; isChunk: boolean }>, k: number ): typeof items { - if (items.length === 0) return items; + if (items.length === 0) { return items; } // If no query embedding, fall back to BM25-only if (!queryEmbedding || queryEmbedding.length === 0) { @@ -1514,7 +1521,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { vectorResults: Array<{ id: string; score: number }>, k: number ): typeof items { - if (items.length === 0) return items; + if (items.length === 0) { return items; } // Create a map of vector store scores by document ID const vectorScoreMap = new Map(); @@ -1581,7 +1588,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { const heapifyUp = (idx: number) => { while (idx > 0) { const parent = Math.floor((idx - 1) / 2); - if (heap[parent].score <= heap[idx].score) break; + if (heap[parent].score <= heap[idx].score) { break; } [heap[parent], heap[idx]] = [heap[idx], heap[parent]]; idx = parent; } @@ -1599,7 +1606,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { if (right < heap.length && heap[right].score < heap[smallest].score) { smallest = right; } - if (smallest === idx) break; + if (smallest === idx) { break; } [heap[idx], heap[smallest]] = [heap[smallest], heap[idx]]; idx = smallest; } @@ -1632,8 +1639,8 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { * Optimized: Use sorted arrays with binary search for large sets (faster than Set.has) */ private _setIntersection(sets: Set[]): Set { - if (sets.length === 0) return new Set(); - if (sets.length === 1) return new Set(sets[0]); + if (sets.length === 0) { return new Set(); } + if (sets.length === 1) { return new Set(sets[0]); } // Start with the smallest set for efficiency sets.sort((a, b) => a.size - b.size); @@ -1673,7 +1680,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { } result = newResult; - if (result.size === 0) break; // Early exit + if (result.size === 0) { break; } // Early exit } return result; @@ -1700,7 +1707,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { } const workspace = this.workspaceContextService.getWorkspace().folders[0]?.uri; - if (!workspace) return; + if (!workspace) { return; } // Watch workspace for file changes (recursive, with exclusions) const watcher = this.fileService.watch(workspace, { @@ -1731,7 +1738,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { private _handleFileChanges(e: FileChangesEvent): void { const workspace = this.workspaceContextService.getWorkspace().folders[0]?.uri; - if (!workspace) return; + if (!workspace) { return; } // Process deleted files (immediate removal) for (const resource of e.rawDeleted) { @@ -1744,10 +1751,10 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { const isOverviewDoc = base === 'readme.md' || base === 'package.json' || base === 'product.json'; const shouldIndex = (ext && codeExts.includes(ext)) || isOverviewDoc; - if (!shouldIndex) continue; + if (!shouldIndex) { continue; } // Skip if not in workspace - if (!path.startsWith(workspace.fsPath)) continue; + if (!path.startsWith(workspace.fsPath)) { continue; } // Find and remove from index const entryIndex = this._index.findIndex(entry => entry.uri === path); @@ -1773,10 +1780,10 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { const isOverviewDoc = base === 'readme.md' || base === 'package.json' || base === 'product.json'; const shouldIndex = (ext && codeExts.includes(ext)) || isOverviewDoc; - if (!shouldIndex) continue; + if (!shouldIndex) { continue; } // Skip if not in workspace - if (!path.startsWith(workspace.fsPath)) continue; + if (!path.startsWith(workspace.fsPath)) { continue; } // Invalidate file content cache for updated files (will be refreshed on next read) this._fileContentCache.delete(path); @@ -1792,7 +1799,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { } private async _processPendingUpdates(): Promise { - if (this._pendingUpdates.size === 0) return; + if (this._pendingUpdates.size === 0) { return; } const urisToUpdate = Array.from(this._pendingUpdates); this._pendingUpdates.clear(); @@ -1916,7 +1923,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { } else if (entry.symbolTokens) { // Token overlap in symbol name (using pre-computed tokens) for (const token of qTokens) { - if (entry.symbolTokens.has(token)) score += 2; + if (entry.symbolTokens.has(token)) { score += 2; } } } } @@ -1931,7 +1938,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { } } else { const uriLower = entry.uri.toLowerCase(); - if (uriLower.includes(qLower)) score += 3; + if (uriLower.includes(qLower)) { score += 3; } } // Lexical overlap in snippet (weighted by token matches) - use pre-computed snippet tokens @@ -1976,7 +1983,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { // very naive token overlap const qt = new Set(q.split(/[^a-z0-9_]+/g).filter(Boolean)); let score = 0; - for (const t of qt) if (doc.includes(t)) score += 1; + for (const t of qt) { if (doc.includes(t)) { score += 1; } } return score; } @@ -2400,7 +2407,7 @@ class RepoIndexerService extends Disposable implements IRepoIndexerService { */ private _removeFromInvertedIndexes(entryIndex: number): void { const entry = this._index[entryIndex]; - if (!entry) return; + if (!entry) { return; } // Remove from path index this._pathIndex.delete(entry.uri.toLowerCase()); diff --git a/src/vs/workbench/contrib/cortexide/browser/sidebarActions.ts b/src/vs/workbench/contrib/cortexide/browser/sidebarActions.ts index 751a72fdbbe..8f701e53fab 100644 --- a/src/vs/workbench/contrib/cortexide/browser/sidebarActions.ts +++ b/src/vs/workbench/contrib/cortexide/browser/sidebarActions.ts @@ -1,7 +1,7 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; @@ -28,27 +28,26 @@ import { IQuickInputService } from '../../../../platform/quickinput/common/quick export const roundRangeToLines = (range: IRange | null | undefined, options: { emptySelectionBehavior: 'null' | 'line' }) => { - if (!range) - return null + if (!range) { return null; } // treat as no selection if selection is empty if (range.endColumn === range.startColumn && range.endLineNumber === range.startLineNumber) { - if (options.emptySelectionBehavior === 'null') - return null - else if (options.emptySelectionBehavior === 'line') - return { startLineNumber: range.startLineNumber, startColumn: 1, endLineNumber: range.startLineNumber, endColumn: 1 } + if (options.emptySelectionBehavior === 'null') { return null; } + else if (options.emptySelectionBehavior === 'line') { return { startLineNumber: range.startLineNumber, startColumn: 1, endLineNumber: range.startLineNumber, endColumn: 1 }; } } // IRange is 1-indexed - const endLine = range.endColumn === 1 ? range.endLineNumber - 1 : range.endLineNumber // e.g. if the user triple clicks, it selects column=0, line=line -> column=0, line=line+1 + // If endColumn is 1, it means the selection ends at the start of the next line + // So we use the previous line. However, ensure endLine >= startLineNumber to avoid invalid ranges + const endLine = range.endColumn === 1 ? Math.max(range.startLineNumber, range.endLineNumber - 1) : range.endLineNumber; // e.g. if the user triple clicks, it selects column=0, line=line -> column=0, line=line+1 const newRange: IRange = { startLineNumber: range.startLineNumber, startColumn: 1, endLineNumber: endLine, endColumn: Number.MAX_SAFE_INTEGER - } - return newRange -} + }; + return newRange; +}; // const getContentInRange = (model: ITextModel, range: IRange | null) => { // if (!range) @@ -62,18 +61,18 @@ export const roundRangeToLines = (range: IRange | null | undefined, options: { e -const CORTEXIDE_OPEN_SIDEBAR_ACTION_ID = 'cortexide.sidebar.open' +const CORTEXIDE_OPEN_SIDEBAR_ACTION_ID = 'cortexide.sidebar.open'; registerAction2(class extends Action2 { constructor() { super({ id: CORTEXIDE_OPEN_SIDEBAR_ACTION_ID, title: localize2('voidOpenSidebar', 'CortexIDE: Open Sidebar'), f1: true }); } async run(accessor: ServicesAccessor): Promise { - const viewsService = accessor.get(IViewsService) - const chatThreadsService = accessor.get(IChatThreadService) - viewsService.openViewContainer(CORTEXIDE_VIEW_CONTAINER_ID) - await chatThreadsService.focusCurrentChat() + const viewsService = accessor.get(IViewsService); + const chatThreadsService = accessor.get(IChatThreadService); + viewsService.openViewContainer(CORTEXIDE_VIEW_CONTAINER_ID); + await chatThreadsService.focusCurrentChat(); } -}) +}); // cmd L @@ -91,27 +90,27 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor): Promise { // Get services - const commandService = accessor.get(ICommandService) - const viewsService = accessor.get(IViewsService) - const metricsService = accessor.get(IMetricsService) - const editorService = accessor.get(ICodeEditorService) - const chatThreadService = accessor.get(IChatThreadService) + const commandService = accessor.get(ICommandService); + const viewsService = accessor.get(IViewsService); + const metricsService = accessor.get(IMetricsService); + const editorService = accessor.get(ICodeEditorService); + const chatThreadService = accessor.get(IChatThreadService); - metricsService.capture('Ctrl+L', {}) + metricsService.capture('Ctrl+L', {}); // capture selection and model before opening the chat panel - const editor = editorService.getActiveCodeEditor() - const model = editor?.getModel() + const editor = editorService.getActiveCodeEditor(); + const model = editor?.getModel(); // open panel - always open even if no editor - const wasAlreadyOpen = viewsService.isViewContainerVisible(CORTEXIDE_VIEW_CONTAINER_ID) + const wasAlreadyOpen = viewsService.isViewContainerVisible(CORTEXIDE_VIEW_CONTAINER_ID); if (!wasAlreadyOpen) { - await commandService.executeCommand(CORTEXIDE_OPEN_SIDEBAR_ACTION_ID) + await commandService.executeCommand(CORTEXIDE_OPEN_SIDEBAR_ACTION_ID); } // If there's a model, add selection to chat if (model) { - const selectionRange = roundRangeToLines(editor?.getSelection(), { emptySelectionBehavior: 'null' }) + const selectionRange = roundRangeToLines(editor?.getSelection(), { emptySelectionBehavior: 'null' }); // add line selection if (selectionRange) { @@ -120,14 +119,14 @@ registerAction2(class extends Action2 { endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER - }) + }); chatThreadService.addNewStagingSelection({ type: 'CodeSelection', uri: model.uri, language: model.getLanguageId(), range: [selectionRange.startLineNumber, selectionRange.endLineNumber], state: { wasAddedAsCurrentFile: false }, - }) + }); } // add file else { @@ -136,17 +135,17 @@ registerAction2(class extends Action2 { uri: model.uri, language: model.getLanguageId(), state: { wasAddedAsCurrentFile: false }, - }) + }); } } - await chatThreadService.focusCurrentChat() + await chatThreadService.focusCurrentChat(); } -}) +}); // New chat keybind + menu button -const CORTEXIDE_CMD_SHIFT_L_ACTION_ID = 'cortexide.cmdShiftL' +const CORTEXIDE_CMD_SHIFT_L_ACTION_ID = 'cortexide.cmdShiftL'; registerAction2(class extends Action2 { constructor() { super({ @@ -162,50 +161,50 @@ registerAction2(class extends Action2 { } async run(accessor: ServicesAccessor): Promise { - const metricsService = accessor.get(IMetricsService) - const chatThreadsService = accessor.get(IChatThreadService) - const editorService = accessor.get(ICodeEditorService) - metricsService.capture('Chat Navigation', { type: 'Start New Chat' }) + const metricsService = accessor.get(IMetricsService); + const chatThreadsService = accessor.get(IChatThreadService); + const editorService = accessor.get(ICodeEditorService); + metricsService.capture('Chat Navigation', { type: 'Start New Chat' }); // get current selections and value to transfer - const oldThreadId = chatThreadsService.state.currentThreadId - const oldThread = chatThreadsService.state.allThreads[oldThreadId] + const oldThreadId = chatThreadsService.state.currentThreadId; + const oldThread = chatThreadsService.state.allThreads[oldThreadId]; - const oldUI = await oldThread?.state.mountedInfo?.whenMounted + const oldUI = await oldThread?.state.mountedInfo?.whenMounted; - const oldSelns = oldThread?.state.stagingSelections - const oldVal = oldUI?.textAreaRef?.current?.value + const oldSelns = oldThread?.state.stagingSelections; + const oldVal = oldUI?.textAreaRef?.current?.value; // open and focus new thread - chatThreadsService.openNewThread() - await chatThreadsService.focusCurrentChat() + chatThreadsService.openNewThread(); + await chatThreadsService.focusCurrentChat(); // set new thread values - const newThreadId = chatThreadsService.state.currentThreadId - const newThread = chatThreadsService.state.allThreads[newThreadId] + const newThreadId = chatThreadsService.state.currentThreadId; + const newThread = chatThreadsService.state.allThreads[newThreadId]; - const newUI = await newThread?.state.mountedInfo?.whenMounted - chatThreadsService.setCurrentThreadState({ stagingSelections: oldSelns, }) - if (newUI?.textAreaRef?.current && oldVal) newUI.textAreaRef.current.value = oldVal + const newUI = await newThread?.state.mountedInfo?.whenMounted; + chatThreadsService.setCurrentThreadState({ stagingSelections: oldSelns, }); + if (newUI?.textAreaRef?.current && oldVal) { newUI.textAreaRef.current.value = oldVal; } // if has selection, add it - const editor = editorService.getActiveCodeEditor() - const model = editor?.getModel() - if (!model) return - const selectionRange = roundRangeToLines(editor?.getSelection(), { emptySelectionBehavior: 'null' }) - if (!selectionRange) return - editor?.setSelection({ startLineNumber: selectionRange.startLineNumber, endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER }) + const editor = editorService.getActiveCodeEditor(); + const model = editor?.getModel(); + if (!model) { return; } + const selectionRange = roundRangeToLines(editor?.getSelection(), { emptySelectionBehavior: 'null' }); + if (!selectionRange) { return; } + editor?.setSelection({ startLineNumber: selectionRange.startLineNumber, endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER }); chatThreadsService.addNewStagingSelection({ type: 'CodeSelection', uri: model.uri, language: model.getLanguageId(), range: [selectionRange.startLineNumber, selectionRange.endLineNumber], state: { wasAddedAsCurrentFile: false }, - }) + }); } -}) +}); // History menu button registerAction2(class extends Action2 { @@ -221,20 +220,20 @@ registerAction2(class extends Action2 { // do not do anything if there are no messages (without this it clears all of the user's selections if the button is pressed) // TODO the history button should be disabled in this case so we can remove this logic - const thread = accessor.get(IChatThreadService).getCurrentThread() + const thread = accessor.get(IChatThreadService).getCurrentThread(); if (thread.messages.length === 0) { return; } - const metricsService = accessor.get(IMetricsService) + const metricsService = accessor.get(IMetricsService); - const commandService = accessor.get(ICommandService) + const commandService = accessor.get(ICommandService); - metricsService.capture('Chat Navigation', { type: 'History' }) - commandService.executeCommand(CORTEXIDE_CMD_SHIFT_L_ACTION_ID) + metricsService.capture('Chat Navigation', { type: 'History' }); + commandService.executeCommand(CORTEXIDE_CMD_SHIFT_L_ACTION_ID); } -}) +}); // Settings gear @@ -248,10 +247,10 @@ registerAction2(class extends Action2 { }); } async run(accessor: ServicesAccessor): Promise { - const commandService = accessor.get(ICommandService) - commandService.executeCommand(CORTEXIDE_TOGGLE_SETTINGS_ACTION_ID) + const commandService = accessor.get(ICommandService); + commandService.executeCommand(CORTEXIDE_TOGGLE_SETTINGS_ACTION_ID); } -}) +}); // Web Search command registerAction2(class extends Action2 { @@ -264,13 +263,13 @@ registerAction2(class extends Action2 { }); } async run(accessor: ServicesAccessor): Promise { - const chatThreadsService = accessor.get(IChatThreadService) - const viewsService = accessor.get(IViewsService) - const quickInputService = accessor.get(IQuickInputService) + const chatThreadsService = accessor.get(IChatThreadService); + const viewsService = accessor.get(IViewsService); + const quickInputService = accessor.get(IQuickInputService); // Open chat sidebar - viewsService.openViewContainer(CORTEXIDE_VIEW_CONTAINER_ID) - await chatThreadsService.focusCurrentChat() + viewsService.openViewContainer(CORTEXIDE_VIEW_CONTAINER_ID); + await chatThreadsService.focusCurrentChat(); // Prompt for search query const query = await quickInputService.input({ @@ -278,15 +277,15 @@ registerAction2(class extends Action2 { prompt: localize2('voidWebSearchPrompt', 'Search the web for information').value, }).then((result: string | undefined) => result); - if (!query) return; + if (!query) { return; } - const threadId = chatThreadsService.state.currentThreadId + const threadId = chatThreadsService.state.currentThreadId; await chatThreadsService.addUserMessageAndStreamResponse({ userMessage: `Search the web for: ${query}`, threadId, - }) + }); } -}) +}); // Browse URL command registerAction2(class extends Action2 { @@ -299,13 +298,13 @@ registerAction2(class extends Action2 { }); } async run(accessor: ServicesAccessor): Promise { - const chatThreadsService = accessor.get(IChatThreadService) - const viewsService = accessor.get(IViewsService) - const quickInputService = accessor.get(IQuickInputService) + const chatThreadsService = accessor.get(IChatThreadService); + const viewsService = accessor.get(IViewsService); + const quickInputService = accessor.get(IQuickInputService); // Open chat sidebar - viewsService.openViewContainer(CORTEXIDE_VIEW_CONTAINER_ID) - await chatThreadsService.focusCurrentChat() + viewsService.openViewContainer(CORTEXIDE_VIEW_CONTAINER_ID); + await chatThreadsService.focusCurrentChat(); // Prompt for URL const url = await quickInputService.input({ @@ -313,15 +312,15 @@ registerAction2(class extends Action2 { prompt: localize2('voidBrowseUrlPrompt', 'Fetch and extract content from URL').value, }).then((result: string | undefined) => result); - if (!url) return; + if (!url) { return; } - const threadId = chatThreadsService.state.currentThreadId + const threadId = chatThreadsService.state.currentThreadId; await chatThreadsService.addUserMessageAndStreamResponse({ userMessage: `Browse URL: ${url}`, threadId, - }) + }); } -}) +}); diff --git a/src/vs/workbench/contrib/cortexide/browser/toolsService.ts b/src/vs/workbench/contrib/cortexide/browser/toolsService.ts index 62c53987014..763755e5351 100644 --- a/src/vs/workbench/contrib/cortexide/browser/toolsService.ts +++ b/src/vs/workbench/contrib/cortexide/browser/toolsService.ts @@ -1,51 +1,56 @@ -import { CancellationToken } from '../../../../base/common/cancellation.js' -import { URI } from '../../../../base/common/uri.js' -import { joinPath } from '../../../../base/common/resources.js' -import { IFileService } from '../../../../platform/files/common/files.js' -import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js' -import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js' -import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js' -import { QueryBuilder } from '../../../services/search/common/queryBuilder.js' -import { ISearchService } from '../../../services/search/common/search.js' -import { IEditCodeService } from './editCodeServiceInterface.js' -import { ITerminalToolService } from './terminalToolService.js' -import { LintErrorItem, BuiltinToolCallParams, BuiltinToolResultType, BuiltinToolName } from '../common/toolsServiceTypes.js' -import { ICortexideModelService } from '../common/cortexideModelService.js' -import { IRepoIndexerService } from './repoIndexerService.js' -import { EndOfLinePreference } from '../../../../editor/common/model.js' -import { ICortexideCommandBarService } from './cortexideCommandBarService.js' -import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree1Deep } from '../common/directoryStrService.js' -import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js' -import { timeout } from '../../../../base/common/async.js' -import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js' -import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_BG_COMMAND_TIME, MAX_TERMINAL_INACTIVE_TIME } from '../common/prompt/prompts.js' -import { ICortexideSettingsService } from '../common/cortexideSettingsService.js' -import { generateUuid } from '../../../../base/common/uuid.js' -import { INotificationService } from '../../../../platform/notification/common/notification.js' -import { IRequestService } from '../../../../platform/request/common/request.js' -import { IWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js' -import { asJson, asTextOrError } from '../../../../platform/request/common/request.js' -import { LRUCache } from '../../../../base/common/map.js' -import { OfflinePrivacyGate } from '../common/offlinePrivacyGate.js' -import { INLShellParserService } from '../common/nlShellParserService.js' -import { ISecretDetectionService } from '../common/secretDetectionService.js' +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { URI } from '../../../../base/common/uri.js'; +import { joinPath } from '../../../../base/common/resources.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { QueryBuilder } from '../../../services/search/common/queryBuilder.js'; +import { ISearchService } from '../../../services/search/common/search.js'; +import { IEditCodeService } from './editCodeServiceInterface.js'; +import { ITerminalToolService } from './terminalToolService.js'; +import { LintErrorItem, BuiltinToolCallParams, BuiltinToolResultType, BuiltinToolName } from '../common/toolsServiceTypes.js'; +import { ICortexideModelService } from '../common/cortexideModelService.js'; +import { IRepoIndexerService } from './repoIndexerService.js'; +import { EndOfLinePreference } from '../../../../editor/common/model.js'; +import { ICortexideCommandBarService } from './cortexideCommandBarService.js'; +import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree1Deep } from '../common/directoryStrService.js'; +import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js'; +import { timeout } from '../../../../base/common/async.js'; +import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js'; +import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_BG_COMMAND_TIME, MAX_TERMINAL_INACTIVE_TIME } from '../common/prompt/prompts.js'; +import { ICortexideSettingsService } from '../common/cortexideSettingsService.js'; +import { generateUuid } from '../../../../base/common/uuid.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IRequestService, asJson, asTextOrError } from '../../../../platform/request/common/request.js'; +import { IWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js'; +import { LRUCache } from '../../../../base/common/map.js'; +import { OfflinePrivacyGate } from '../common/offlinePrivacyGate.js'; +import { INLShellParserService } from '../common/nlShellParserService.js'; +import { ISecretDetectionService } from '../common/secretDetectionService.js'; // tool use for AI -type ValidateBuiltinParams = { [T in BuiltinToolName]: (p: RawToolParamsObj) => BuiltinToolCallParams[T] } -type CallBuiltinTool = { [T in BuiltinToolName]: (p: BuiltinToolCallParams[T]) => Promise<{ result: BuiltinToolResultType[T] | Promise, interruptTool?: () => void }> } -type BuiltinToolResultToString = { [T in BuiltinToolName]: (p: BuiltinToolCallParams[T], result: Awaited) => string } +type ValidateBuiltinParams = { [T in BuiltinToolName]: (p: RawToolParamsObj) => BuiltinToolCallParams[T] }; +type CallBuiltinTool = { [T in BuiltinToolName]: (p: BuiltinToolCallParams[T]) => Promise<{ result: BuiltinToolResultType[T] | Promise; interruptTool?: () => void }> }; +type BuiltinToolResultToString = { [T in BuiltinToolName]: (p: BuiltinToolCallParams[T], result: Awaited) => string }; const isFalsy = (u: unknown) => { - return !u || u === 'null' || u === 'undefined' -} + return !u || u === 'null' || u === 'undefined'; +}; -const validateStr = (argName: string, value: unknown) => { - if (value === null) throw new Error(`Invalid LLM output: ${argName} was null.`) - if (typeof value !== 'string') throw new Error(`Invalid LLM output format: ${argName} must be a string, but its type is "${typeof value}". Full value: ${JSON.stringify(value)}.`) - return value -} +const validateStr = (argName: string, value: unknown, allowEmpty: boolean = true) => { + if (value === null) { throw new Error(`Invalid LLM output: ${argName} was null.`); } + if (typeof value !== 'string') { throw new Error(`Invalid LLM output format: ${argName} must be a string, but its type is "${typeof value}". Full value: ${JSON.stringify(value)}.`); } + if (!allowEmpty && value.trim().length === 0) { throw new Error(`Invalid LLM output: ${argName} cannot be empty.`); } + return value; +}; /** @@ -53,123 +58,177 @@ const validateStr = (argName: string, value: unknown) => { * Now includes workspace validation for safety in Agent Mode. */ const validateURI = (uriStr: unknown, workspaceContextService?: IWorkspaceContextService, requireWorkspace: boolean = true) => { - if (uriStr === null) throw new Error(`Invalid LLM output: uri was null.`) - if (typeof uriStr !== 'string') throw new Error(`Invalid LLM output format: Provided uri must be a string, but it's a(n) ${typeof uriStr}. Full value: ${JSON.stringify(uriStr)}.`) + if (uriStr === null) { throw new Error(`Invalid LLM output: uri was null.`); } + if (typeof uriStr !== 'string') { throw new Error(`Invalid LLM output format: Provided uri must be a string, but it's a(n) ${typeof uriStr}. Full value: ${JSON.stringify(uriStr)}.`); } + + // Normalize the path string first + let normalizedPath = uriStr.trim(); + + // Check for invalid root path + if (normalizedPath === '/' || normalizedPath === '') { + throw new Error(`Invalid file path: "${uriStr}" is not a valid file path. Please provide a specific file or directory path.`); + } + + // Check for paths that are only whitespace + if (normalizedPath.length === 0 || /^\s+$/.test(normalizedPath)) { + throw new Error(`Invalid file path: Path contains only whitespace. Please provide a valid file or directory path.`); + } + + // Normalize path separators and remove double slashes (but preserve leading // on Windows UNC paths) + // Replace backslashes with forward slashes for cross-platform compatibility + normalizedPath = normalizedPath.replace(/\\/g, '/'); + // Remove double slashes, but preserve leading // for UNC paths and :// for protocol + // Only collapse slashes if it doesn't start with // (UNC) and doesn't contain :// (protocol) + if (!normalizedPath.startsWith('//') && !normalizedPath.includes('://')) { + normalizedPath = normalizedPath.replace(/\/+/g, '/'); + } else if (normalizedPath.includes('://')) { + // For protocol URIs, collapse slashes but preserve :// + const [scheme, rest] = normalizedPath.split('://', 2); + normalizedPath = scheme + '://' + rest.replace(/\/+/g, '/'); + } + // Remove trailing slashes (except for root paths) + if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) { + normalizedPath = normalizedPath.slice(0, -1); + } let uri: URI; // Check if it's already a full URI with scheme (e.g., vscode-remote://, file://, etc.) - if (uriStr.includes('://')) { + if (normalizedPath.includes('://')) { try { - uri = URI.parse(uriStr) + uri = URI.parse(normalizedPath); } catch (e) { - throw new Error(`Invalid URI format: ${uriStr}. Error: ${e}`) + throw new Error(`Invalid URI format: ${normalizedPath}. Error: ${e}`); } } else { // No scheme present, treat as file path - uri = URI.file(uriStr); + // Use normalized path + uri = URI.file(normalizedPath); + + // Check for dangerous path patterns + // Reject paths with .. that would escape workspace + if (normalizedPath.includes('../') || normalizedPath === '..' || normalizedPath.endsWith('/..')) { + throw new Error(`Invalid file path: "${normalizedPath}" contains parent directory references (..) which are not allowed for security. Use a relative path without ..`); + } // If we have a workspace and the path is relative (doesn't start with /), resolve it - if (workspaceContextService && !uriStr.startsWith('/')) { + if (workspaceContextService && !normalizedPath.startsWith('/')) { const workspace = workspaceContextService.getWorkspace(); if (workspace.folders.length > 0) { // Resolve relative path against workspace root - uri = joinPath(workspace.folders[0].uri, uriStr); + uri = joinPath(workspace.folders[0].uri, normalizedPath); } } // If path is absolute (starts with /), check if it's actually within workspace // This handles cases where LLM returns paths like "/carepilot-api/src" that should be relative - else if (workspaceContextService && uriStr.startsWith('/')) { + else if (workspaceContextService && normalizedPath.startsWith('/')) { const workspace = workspaceContextService.getWorkspace(); + let resolved = false; for (const folder of workspace.folders) { const workspacePath = folder.uri.fsPath; // Check if the absolute path is actually within this workspace folder // by checking if workspace path is a prefix - if (uriStr.startsWith(workspacePath)) { + if (normalizedPath.startsWith(workspacePath)) { // Path is already correctly absolute within workspace + resolved = true; break; } // Check if path starts with workspace folder name (common LLM mistake) const workspaceFolderName = folder.name || folder.uri.path.split('/').pop() || ''; - if (uriStr.startsWith(`/${workspaceFolderName}/`) || uriStr === `/${workspaceFolderName}`) { + if (normalizedPath.startsWith(`/${workspaceFolderName}/`) || normalizedPath === `/${workspaceFolderName}`) { // Treat as relative path - remove leading slash and folder name - const relativePath = uriStr.replace(`/${workspaceFolderName}`, '').replace(/^\//, ''); + const relativePath = normalizedPath.replace(`/${workspaceFolderName}`, '').replace(/^\//, ''); uri = joinPath(folder.uri, relativePath); + resolved = true; break; } } + // If path starts with / but doesn't match workspace, treat as relative path + // This handles cases where LLM thinks "/auth-api/" means relative to workspace root + if (!resolved && workspace.folders.length > 0) { + // Remove leading slash and treat as relative to workspace root + const relativePath = normalizedPath.replace(/^\//, ''); + uri = joinPath(workspace.folders[0].uri, relativePath); + } } } + // Normalize the URI to ensure consistent format (removes redundant slashes, normalizes path) + // This helps prevent issues where the same file might be referenced with different URI formats + uri = uri.with({ path: uri.path.replace(/\/+/g, '/').replace(/\/$/, '') || '/' }); + // Strict workspace enforcement for Agent Mode safety if (requireWorkspace && workspaceContextService) { + const workspace = workspaceContextService.getWorkspace(); + // If no workspace is open, provide a helpful error + if (workspace.folders.length === 0) { + throw new Error(`Cannot access file "${uri.fsPath}" because no workspace is open. Please open a workspace folder first.`); + } const isInWorkspace = workspaceContextService.isInsideWorkspace(uri); if (!isInWorkspace) { // Provide helpful error message with workspace info - const workspace = workspaceContextService.getWorkspace(); const workspaceFolders = workspace.folders.map(f => f.uri.fsPath).join(', '); throw new Error(`File ${uri.fsPath} is outside the workspace and cannot be accessed. Only files within the workspace are allowed for safety. Current workspace: ${workspaceFolders || 'none'}. If this is a relative path, ensure it's relative to the workspace root.`); } } return uri; -} +}; const validateOptionalURI = (uriStr: unknown, workspaceContextService?: IWorkspaceContextService) => { - if (isFalsy(uriStr)) return null - return validateURI(uriStr, workspaceContextService, true) -} + if (isFalsy(uriStr)) { return null; } + return validateURI(uriStr, workspaceContextService, true); +}; const validateOptionalStr = (argName: string, str: unknown) => { - if (isFalsy(str)) return null - return validateStr(argName, str) -} + if (isFalsy(str)) { return null; } + return validateStr(argName, str); +}; const validatePageNum = (pageNumberUnknown: unknown) => { - if (!pageNumberUnknown) return 1 - const parsedInt = Number.parseInt(pageNumberUnknown + '') - if (!Number.isInteger(parsedInt)) throw new Error(`Page number was not an integer: "${pageNumberUnknown}".`) - if (parsedInt < 1) throw new Error(`Invalid LLM output format: Specified page number must be 1 or greater: "${pageNumberUnknown}".`) - return parsedInt -} + if (!pageNumberUnknown) { return 1; } + const parsedInt = Number.parseInt(pageNumberUnknown + ''); + if (!Number.isInteger(parsedInt)) { throw new Error(`Page number was not an integer: "${pageNumberUnknown}".`); } + if (parsedInt < 1) { throw new Error(`Invalid LLM output format: Specified page number must be 1 or greater: "${pageNumberUnknown}".`); } + return parsedInt; +}; const validateNumber = (numStr: unknown, opts: { default: number | null }) => { - if (typeof numStr === 'number') - return numStr - if (isFalsy(numStr)) return opts.default + if (typeof numStr === 'number') { return numStr; } + if (isFalsy(numStr)) { return opts.default; } if (typeof numStr === 'string') { - const parsedInt = Number.parseInt(numStr + '') - if (!Number.isInteger(parsedInt)) return opts.default - return parsedInt + const parsedInt = Number.parseInt(numStr + ''); + if (!Number.isInteger(parsedInt)) { return opts.default; } + return parsedInt; } - return opts.default -} + return opts.default; +}; const validateProposedTerminalId = (terminalIdUnknown: unknown) => { - if (!terminalIdUnknown) throw new Error(`A value for terminalID must be specified, but the value was "${terminalIdUnknown}"`) - const terminalId = terminalIdUnknown + '' - return terminalId -} + if (!terminalIdUnknown) { throw new Error(`A value for terminalID must be specified, but the value was "${terminalIdUnknown}"`); } + const terminalId = terminalIdUnknown + ''; + return terminalId; +}; const validateBoolean = (b: unknown, opts: { default: boolean }) => { if (typeof b === 'string') { - if (b === 'true') return true - if (b === 'false') return false + if (b === 'true') { return true; } + if (b === 'false') { return false; } } if (typeof b === 'boolean') { - return b + return b; } - return opts.default -} + return opts.default; +}; const checkIfIsFolder = (uriStr: string) => { - uriStr = uriStr.trim() - if (uriStr.endsWith('/') || uriStr.endsWith('\\')) return true - return false -} + uriStr = uriStr.trim(); + if (uriStr.endsWith('/') || uriStr.endsWith('\\')) { return true; } + return false; +}; export interface IToolsService { readonly _serviceBrand: undefined; @@ -188,8 +247,8 @@ export class ToolsService implements IToolsService { public callTool: CallBuiltinTool; public stringOfResult: BuiltinToolResultToString; - private readonly _webSearchCache = new LRUCache, timestamp: number }>(100); - private readonly _browseCache = new LRUCache(100); + private readonly _webSearchCache = new LRUCache; timestamp: number }>(100); + private readonly _browseCache = new LRUCache(100); private readonly _cacheTTL = 60 * 60 * 1000; // 1 hour private readonly _offlineGate: OfflinePrivacyGate; @@ -217,42 +276,45 @@ export class ToolsService implements IToolsService { this.validateParams = { read_file: (params: RawToolParamsObj) => { - const { uri: uriStr, start_line: startLineUnknown, end_line: endLineUnknown, page_number: pageNumberUnknown } = params - const uri = validateURI(uriStr, workspaceContextService, true) - const pageNumber = validatePageNum(pageNumberUnknown) + const { uri: uriStr, start_line: startLineUnknown, end_line: endLineUnknown, page_number: pageNumberUnknown } = params; + const uri = validateURI(uriStr, workspaceContextService, true); + const pageNumber = validatePageNum(pageNumberUnknown); - let startLine = validateNumber(startLineUnknown, { default: null }) - let endLine = validateNumber(endLineUnknown, { default: null }) + let startLine = validateNumber(startLineUnknown, { default: null }); + let endLine = validateNumber(endLineUnknown, { default: null }); - if (startLine !== null && startLine < 1) startLine = null - if (endLine !== null && endLine < 1) endLine = null + if (startLine !== null && startLine < 1) { startLine = null; } + if (endLine !== null && endLine < 1) { endLine = null; } - return { uri, startLine, endLine, pageNumber } + return { uri, startLine, endLine, pageNumber }; }, ls_dir: (params: RawToolParamsObj) => { - const { uri: uriStr, page_number: pageNumberUnknown } = params + const { uri: uriStr, page_number: pageNumberUnknown } = params; - const uri = validateURI(uriStr, workspaceContextService, true) - const pageNumber = validatePageNum(pageNumberUnknown) - return { uri, pageNumber } + const uri = validateURI(uriStr, workspaceContextService, true); + const pageNumber = validatePageNum(pageNumberUnknown); + return { uri, pageNumber }; }, get_dir_tree: (params: RawToolParamsObj) => { - const { uri: uriStr, } = params - const uri = validateURI(uriStr, workspaceContextService, true) - return { uri } + const { uri: uriStr, } = params; + const uri = validateURI(uriStr, workspaceContextService, true); + return { uri }; }, search_pathnames_only: (params: RawToolParamsObj) => { const { query: queryUnknown, search_in_folder: includeUnknown, page_number: pageNumberUnknown - } = params + } = params; - const queryStr = validateStr('query', queryUnknown) - const pageNumber = validatePageNum(pageNumberUnknown) - const includePattern = validateOptionalStr('include_pattern', includeUnknown) + const queryStr = validateStr('query', queryUnknown, false); + if (queryStr.trim().length === 0) { + throw new Error('Invalid query: Search query cannot be empty. Please provide a non-empty search term.'); + } + const pageNumber = validatePageNum(pageNumberUnknown); + const includePattern = validateOptionalStr('include_pattern', includeUnknown); - return { query: queryStr, includePattern, pageNumber } + return { query: queryStr.trim(), includePattern, pageNumber }; }, search_for_files: (params: RawToolParamsObj) => { @@ -261,94 +323,140 @@ export class ToolsService implements IToolsService { search_in_folder: searchInFolderUnknown, is_regex: isRegexUnknown, page_number: pageNumberUnknown - } = params - const queryStr = validateStr('query', queryUnknown) - const pageNumber = validatePageNum(pageNumberUnknown) - const searchInFolder = validateOptionalURI(searchInFolderUnknown, workspaceContextService) - const isRegex = validateBoolean(isRegexUnknown, { default: false }) + } = params; + const queryStr = validateStr('query', queryUnknown, false); + if (queryStr.trim().length === 0) { + throw new Error('Invalid query: Search query cannot be empty. Please provide a non-empty search term.'); + } + const pageNumber = validatePageNum(pageNumberUnknown); + const searchInFolder = validateOptionalURI(searchInFolderUnknown, workspaceContextService); + const isRegex = validateBoolean(isRegexUnknown, { default: false }); return { - query: queryStr, + query: queryStr.trim(), isRegex, searchInFolder, pageNumber - } + }; }, search_in_file: (params: RawToolParamsObj) => { const { uri: uriStr, query: queryUnknown, is_regex: isRegexUnknown } = params; const uri = validateURI(uriStr, workspaceContextService, true); - const query = validateStr('query', queryUnknown); + const query = validateStr('query', queryUnknown, false); + if (query.trim().length === 0) { + throw new Error('Invalid query: Search query cannot be empty. Please provide a non-empty search term.'); + } const isRegex = validateBoolean(isRegexUnknown, { default: false }); - return { uri, query, isRegex }; + return { uri, query: query.trim(), isRegex }; }, read_lint_errors: (params: RawToolParamsObj) => { const { uri: uriUnknown, - } = params - const uri = validateURI(uriUnknown, workspaceContextService, true) - return { uri } + } = params; + const uri = validateURI(uriUnknown, workspaceContextService, true); + return { uri }; }, // --- create_file_or_folder: (params: RawToolParamsObj) => { - const { uri: uriUnknown } = params - const uri = validateURI(uriUnknown, workspaceContextService, true) - const uriStr = validateStr('uri', uriUnknown) - const isFolder = checkIfIsFolder(uriStr) - return { uri, isFolder } + const { uri: uriUnknown } = params; + const uri = validateURI(uriUnknown, workspaceContextService, true); + const uriStr = validateStr('uri', uriUnknown); + const isFolder = checkIfIsFolder(uriStr); + return { uri, isFolder }; }, delete_file_or_folder: (params: RawToolParamsObj) => { - const { uri: uriUnknown, is_recursive: isRecursiveUnknown } = params - const uri = validateURI(uriUnknown, workspaceContextService, true) - const isRecursive = validateBoolean(isRecursiveUnknown, { default: false }) - const uriStr = validateStr('uri', uriUnknown) - const isFolder = checkIfIsFolder(uriStr) - return { uri, isRecursive, isFolder } + const { uri: uriUnknown, is_recursive: isRecursiveUnknown } = params; + const uri = validateURI(uriUnknown, workspaceContextService, true); + const isRecursive = validateBoolean(isRecursiveUnknown, { default: false }); + const uriStr = validateStr('uri', uriUnknown); + const isFolder = checkIfIsFolder(uriStr); + return { uri, isRecursive, isFolder }; }, rewrite_file: (params: RawToolParamsObj) => { - const { uri: uriStr, new_content: newContentUnknown } = params - const uri = validateURI(uriStr, workspaceContextService, true) - const newContent = validateStr('newContent', newContentUnknown) - return { uri, newContent } + const { uri: uriStr, new_content: newContentUnknown } = params; + const uri = validateURI(uriStr, workspaceContextService, true); + // Allow empty content for rewrite_file (user might want to clear file) + const newContent = validateStr('newContent', newContentUnknown, true); + return { uri, newContent }; }, edit_file: (params: RawToolParamsObj) => { - const { uri: uriStr, search_replace_blocks: searchReplaceBlocksUnknown } = params - const uri = validateURI(uriStr, workspaceContextService, true) - const searchReplaceBlocks = validateStr('searchReplaceBlocks', searchReplaceBlocksUnknown) - return { uri, searchReplaceBlocks } + const { uri: uriStr, search_replace_blocks: searchReplaceBlocksUnknown } = params; + const uri = validateURI(uriStr, workspaceContextService, true); + const searchReplaceBlocks = validateStr('searchReplaceBlocks', searchReplaceBlocksUnknown, false); + if (searchReplaceBlocks.trim().length === 0) { + throw new Error('Invalid searchReplaceBlocks: Cannot be empty. Please provide valid search and replace blocks.'); + } + return { uri, searchReplaceBlocks: searchReplaceBlocks.trim() }; }, // --- run_command: (params: RawToolParamsObj) => { - const { command: commandUnknown, cwd: cwdUnknown } = params - const command = validateStr('command', commandUnknown) - const cwd = validateOptionalStr('cwd', cwdUnknown) - const terminalId = generateUuid() - return { command, cwd, terminalId } + const { command: commandUnknown, cwd: cwdUnknown } = params; + const command = validateStr('command', commandUnknown, false); + if (command.trim().length === 0) { + throw new Error('Invalid command: Command cannot be empty. Please provide a valid command to execute.'); + } + const cwd = validateOptionalStr('cwd', cwdUnknown); + // Validate cwd if provided - it should be a valid path within workspace + if (cwd !== null && cwd.trim().length > 0) { + try { + validateURI(cwd.trim(), workspaceContextService, true); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + throw new Error(`Invalid cwd (current working directory): ${errorMsg}`); + } + } + const terminalId = generateUuid(); + return { command: command.trim(), cwd: cwd?.trim() || null, terminalId }; }, run_nl_command: (params: RawToolParamsObj) => { - const { nl_input: nlInputUnknown, cwd: cwdUnknown } = params - const nlInput = validateStr('nl_input', nlInputUnknown) - const cwd = validateOptionalStr('cwd', cwdUnknown) - const terminalId = generateUuid() - return { nlInput, cwd, terminalId } + const { nl_input: nlInputUnknown, cwd: cwdUnknown } = params; + const nlInput = validateStr('nl_input', nlInputUnknown, false); + if (nlInput.trim().length === 0) { + throw new Error('Invalid nl_input: Natural language command cannot be empty. Please provide a valid command description.'); + } + const cwd = validateOptionalStr('cwd', cwdUnknown); + // Validate cwd if provided - it should be a valid path within workspace + if (cwd !== null && cwd.trim().length > 0) { + try { + validateURI(cwd.trim(), workspaceContextService, true); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + throw new Error(`Invalid cwd (current working directory): ${errorMsg}`); + } + } + const terminalId = generateUuid(); + return { nlInput: nlInput.trim(), cwd: cwd?.trim() || null, terminalId }; }, run_persistent_command: (params: RawToolParamsObj) => { const { command: commandUnknown, persistent_terminal_id: persistentTerminalIdUnknown } = params; - const command = validateStr('command', commandUnknown); - const persistentTerminalId = validateProposedTerminalId(persistentTerminalIdUnknown) - return { command, persistentTerminalId }; + const command = validateStr('command', commandUnknown, false); + if (command.trim().length === 0) { + throw new Error('Invalid command: Command cannot be empty. Please provide a valid command to execute.'); + } + const persistentTerminalId = validateProposedTerminalId(persistentTerminalIdUnknown); + return { command: command.trim(), persistentTerminalId }; }, open_persistent_terminal: (params: RawToolParamsObj) => { const { cwd: cwdUnknown } = params; - const cwd = validateOptionalStr('cwd', cwdUnknown) + const cwd = validateOptionalStr('cwd', cwdUnknown); + // Validate cwd if provided - it should be a valid path within workspace + if (cwd !== null && cwd.trim().length > 0) { + try { + validateURI(cwd.trim(), workspaceContextService, true); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + throw new Error(`Invalid cwd (current working directory): ${errorMsg}`); + } + } // No parameters needed; will open a new background terminal - return { cwd }; + return { cwd: cwd?.trim() || null }; }, kill_persistent_terminal: (params: RawToolParamsObj) => { const { persistent_terminal_id: terminalIdUnknown } = params; @@ -361,6 +469,10 @@ export class ToolsService implements IToolsService { web_search: (params: RawToolParamsObj) => { const { query: queryUnknown, k: kUnknown, refresh: refreshUnknown } = params; const query = validateStr('query', queryUnknown); + // Validate query is not empty after trimming + if (query.trim().length === 0) { + throw new Error('Invalid query: Search query cannot be empty. Please provide a non-empty search term.'); + } const k = validateNumber(kUnknown, { default: 5 }); if (k === null) { throw new Error('Invalid k parameter for web_search'); @@ -370,82 +482,98 @@ export class ToolsService implements IToolsService { if (refreshUnknown && typeof refreshUnknown === 'string') { refresh = refreshUnknown.toLowerCase() === 'true'; } - return { query, k: validK, refresh }; + return { query: query.trim(), k: validK, refresh }; }, browse_url: (params: RawToolParamsObj) => { const { url: urlUnknown, refresh: refreshUnknown } = params; - const url = validateStr('url', urlUnknown); + const url = validateStr('url', urlUnknown, false); + const trimmedUrl = url.trim(); + if (trimmedUrl.length === 0) { + throw new Error('Invalid URL: URL cannot be empty. Please provide a valid URL.'); + } // Basic URL validation - if (!url.startsWith('http://') && !url.startsWith('https://')) { - throw new Error(`Invalid URL format: ${url}. URL must start with http:// or https://`); + if (!trimmedUrl.startsWith('http://') && !trimmedUrl.startsWith('https://')) { + throw new Error(`Invalid URL format: ${trimmedUrl}. URL must start with http:// or https://`); } try { - new URL(url); // Validate URL format + new URL(trimmedUrl); // Validate URL format } catch (e) { - throw new Error(`Invalid URL format: ${url}. Error: ${e}`); + const errorMsg = e instanceof Error ? e.message : String(e); + throw new Error(`Invalid URL format: ${trimmedUrl}. Error: ${errorMsg}`); } let refresh = false; if (refreshUnknown && typeof refreshUnknown === 'string') { refresh = refreshUnknown.toLowerCase() === 'true'; } - return { url, refresh }; + return { url: trimmedUrl, refresh }; }, - } + }; this.callTool = { read_file: async ({ uri, startLine, endLine, pageNumber }) => { - await cortexideModelService.initializeModel(uri) - let { model } = await cortexideModelService.getModelSafe(uri) + await cortexideModelService.initializeModel(uri); + let { model } = await cortexideModelService.getModelSafe(uri); if (model === null) { // Fallback: try to locate the file within the workspace by basename (grep-like) - const requestedName = uri.fsPath.split(/[/\\]/).pop() || uri.fsPath + const requestedName = uri.fsPath.split(/[/\\]/).pop() || uri.fsPath; try { const query = queryBuilder.file(workspaceContextService.getWorkspace().folders.map(f => f.uri), { filePattern: requestedName, sortByScore: true, - }) - const data = await searchService.fileSearch(query, CancellationToken.None) - const fallback = data.results[0]?.resource + }); + const data = await searchService.fileSearch(query, CancellationToken.None); + const fallback = data.results[0]?.resource; if (fallback) { - uri = fallback - await cortexideModelService.initializeModel(uri) - model = (await cortexideModelService.getModelSafe(uri)).model + uri = fallback; + await cortexideModelService.initializeModel(uri); + model = (await cortexideModelService.getModelSafe(uri)).model; } } catch { /* ignore and throw original error if still null */ } - if (model === null) { throw new Error(`No contents; File does not exist.`) } + if (model === null) { throw new Error(`No contents; File does not exist.`); } } - let contents: string + const totalNumLines = model.getLineCount(); + + // Validate line numbers are within valid range + if (startLine !== null && (startLine < 1 || startLine > totalNumLines)) { + throw new Error(`Invalid startLine: ${startLine}. Line number must be between 1 and ${totalNumLines} (file has ${totalNumLines} lines).`); + } + if (endLine !== null && (endLine < 1 || endLine > totalNumLines)) { + throw new Error(`Invalid endLine: ${endLine}. Line number must be between 1 and ${totalNumLines} (file has ${totalNumLines} lines).`); + } + if (startLine !== null && endLine !== null && startLine > endLine) { + throw new Error(`Invalid line range: startLine (${startLine}) must be less than or equal to endLine (${endLine}).`); + } + + let contents: string; if (startLine === null && endLine === null) { - contents = model.getValue(EndOfLinePreference.LF) + contents = model.getValue(EndOfLinePreference.LF); } else { - const startLineNumber = startLine === null ? 1 : startLine - const endLineNumber = endLine === null ? model.getLineCount() : endLine - contents = model.getValueInRange({ startLineNumber, startColumn: 1, endLineNumber, endColumn: Number.MAX_SAFE_INTEGER }, EndOfLinePreference.LF) + const startLineNumber = startLine === null ? 1 : startLine; + const endLineNumber = endLine === null ? totalNumLines : endLine; + contents = model.getValueInRange({ startLineNumber, startColumn: 1, endLineNumber, endColumn: Number.MAX_SAFE_INTEGER }, EndOfLinePreference.LF); } - const totalNumLines = model.getLineCount() - - const fromIdx = MAX_FILE_CHARS_PAGE * (pageNumber - 1) - const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1 - const fileContents = contents.slice(fromIdx, toIdx + 1) // paginate - const hasNextPage = (contents.length - 1) - toIdx >= 1 - const totalFileLen = contents.length - return { result: { fileContents, totalFileLen, hasNextPage, totalNumLines } } + const fromIdx = MAX_FILE_CHARS_PAGE * (pageNumber - 1); + const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1; + const fileContents = contents.slice(fromIdx, toIdx + 1); // paginate + const hasNextPage = (contents.length - 1) - toIdx >= 1; + const totalFileLen = contents.length; + return { result: { fileContents, totalFileLen, hasNextPage, totalNumLines } }; }, ls_dir: async ({ uri, pageNumber }) => { - const dirResult = await computeDirectoryTree1Deep(fileService, uri, pageNumber) - return { result: dirResult } + const dirResult = await computeDirectoryTree1Deep(fileService, uri, pageNumber); + return { result: dirResult }; }, get_dir_tree: async ({ uri }) => { - const str = await this.directoryStrService.getDirectoryStrTool(uri) - return { result: { str } } + const str = await this.directoryStrService.getDirectoryStrTool(uri); + return { result: { str } }; }, search_pathnames_only: async ({ query: queryStr, includePattern, pageNumber }) => { @@ -454,78 +582,88 @@ export class ToolsService implements IToolsService { filePattern: queryStr, includePattern: includePattern ?? undefined, sortByScore: true, // makes results 10x better - }) - const data = await searchService.fileSearch(query, CancellationToken.None) + }); + const data = await searchService.fileSearch(query, CancellationToken.None); - const fromIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1) - const toIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1 + const fromIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1); + const toIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1; const uris = data.results .slice(fromIdx, toIdx + 1) // paginate - .map(({ resource, results }) => resource) + .map(({ resource, results }) => resource); - const hasNextPage = (data.results.length - 1) - toIdx >= 1 - return { result: { uris, hasNextPage } } + const hasNextPage = (data.results.length - 1) - toIdx >= 1; + return { result: { uris, hasNextPage } }; }, search_for_files: async ({ query: queryStr, isRegex, searchInFolder, pageNumber }) => { + // Validate regex pattern if isRegex is true + if (isRegex) { + try { + new RegExp(queryStr); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + throw new Error(`Invalid regex pattern: "${queryStr}". Error: ${errorMsg}. Please provide a valid regular expression.`); + } + } + // Try indexer first for non-regex, whole-workspace queries - let indexedUris: URI[] | null = null + let indexedUris: URI[] | null = null; if (!isRegex && searchInFolder === null) { try { - const k = MAX_CHILDREN_URIs_PAGE * pageNumber - const results = await this.repoIndexerService.query(queryStr, k) + const k = MAX_CHILDREN_URIs_PAGE * pageNumber; + const results = await this.repoIndexerService.query(queryStr, k); if (results && results.length) { - indexedUris = results.map(p => URI.file(p)) + indexedUris = results.map(p => URI.file(p)); } } catch { /* ignore and fall back */ } } if (indexedUris && indexedUris.length) { - const fromIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1) - const toIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1 - const paged = indexedUris.slice(fromIdx, toIdx + 1) - const hasNextPage = (indexedUris.length - 1) - toIdx >= 1 - return { result: { queryStr, uris: paged, hasNextPage } } + const fromIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1); + const toIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1; + const paged = indexedUris.slice(fromIdx, toIdx + 1); + const hasNextPage = (indexedUris.length - 1) - toIdx >= 1; + return { result: { queryStr, uris: paged, hasNextPage } }; } // Fallback: ripgrep-backed text search const searchFolders = searchInFolder === null ? workspaceContextService.getWorkspace().folders.map(f => f.uri) - : [searchInFolder] + : [searchInFolder]; const query = queryBuilder.text({ pattern: queryStr, isRegExp: isRegex, - }, searchFolders) + }, searchFolders); - const data = await searchService.textSearch(query, CancellationToken.None) + const data = await searchService.textSearch(query, CancellationToken.None); - const fromIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1) - const toIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1 + const fromIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1); + const toIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1; const uris = data.results .slice(fromIdx, toIdx + 1) // paginate - .map(({ resource, results }) => resource) + .map(({ resource, results }) => resource); - const hasNextPage = (data.results.length - 1) - toIdx >= 1 - return { result: { queryStr, uris, hasNextPage } } + const hasNextPage = (data.results.length - 1) - toIdx >= 1; + return { result: { queryStr, uris, hasNextPage } }; }, search_in_file: async ({ uri, query, isRegex }) => { await cortexideModelService.initializeModel(uri); let { model } = await cortexideModelService.getModelSafe(uri); if (model === null) { // Fallback: try to locate the file within the workspace by basename (grep-like) - const requestedName = uri.fsPath.split(/[/\\]/).pop() || uri.fsPath + const requestedName = uri.fsPath.split(/[/\\]/).pop() || uri.fsPath; try { const query_ = queryBuilder.file(workspaceContextService.getWorkspace().folders.map(f => f.uri), { filePattern: requestedName, sortByScore: true, - }) - const data = await searchService.fileSearch(query_, CancellationToken.None) - const fallback = data.results[0]?.resource + }); + const data = await searchService.fileSearch(query_, CancellationToken.None); + const fallback = data.results[0]?.resource; if (fallback) { - uri = fallback - await cortexideModelService.initializeModel(uri) - model = (await cortexideModelService.getModelSafe(uri)).model + uri = fallback; + await cortexideModelService.initializeModel(uri); + model = (await cortexideModelService.getModelSafe(uri)).model; } } catch { /* ignore and throw original error if still null */ } if (model === null) { throw new Error(`No contents; File does not exist.`); } @@ -533,8 +671,19 @@ export class ToolsService implements IToolsService { const contents = model.getValue(EndOfLinePreference.LF); const contentOfLine = contents.split('\n'); const totalLines = contentOfLine.length; - const regex = isRegex ? new RegExp(query) : null; - const lines: number[] = [] + + // Validate regex pattern if isRegex is true + let regex: RegExp | null = null; + if (isRegex) { + try { + regex = new RegExp(query); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + throw new Error(`Invalid regex pattern: "${query}". Error: ${errorMsg}. Please provide a valid regular expression.`); + } + } + + const lines: number[] = []; for (let i = 0; i < totalLines; i++) { const line = contentOfLine[i]; if ((isRegex && regex!.test(line)) || (!isRegex && line.includes(query))) { @@ -546,59 +695,163 @@ export class ToolsService implements IToolsService { }, read_lint_errors: async ({ uri }) => { - await timeout(1000) - const { lintErrors } = this._getLintErrors(uri) - return { result: { lintErrors } } + await timeout(1000); + const { lintErrors } = this._getLintErrors(uri); + return { result: { lintErrors } }; }, // --- create_file_or_folder: async ({ uri, isFolder }) => { - if (isFolder) - await fileService.createFolder(uri) - else { - await fileService.createFile(uri) + if (isFolder) { + await fileService.createFolder(uri); + } else { + // Check if file already exists + const fileExists = await fileService.exists(uri); + if (fileExists) { + // Check if it's actually a file or a directory + try { + const stat = await fileService.stat(uri); + if (stat.isDirectory) { + throw new Error(`Cannot create file at ${uri.fsPath}: A directory with this name already exists.`); + } + // File exists - this is okay, we'll overwrite it + } catch (error) { + if (error instanceof Error && error.message.includes('directory')) { + throw error; + } + // Other errors, continue with creation + } + } + + // Ensure parent directory exists and is actually a directory + // Handle edge case: if path is root or has no parent, skip parent directory check + const pathWithoutBasename = uri.path.replace(/\/[^/]*$/, ''); + if (pathWithoutBasename === '' || pathWithoutBasename === '/') { + // Root path or no parent - skip parent directory validation + // This handles cases like creating files at workspace root + } else { + const parentDir = uri.with({ path: pathWithoutBasename || '/' }); + try { + const parentStat = await fileService.stat(parentDir); + if (!parentStat.isDirectory) { + throw new Error(`Cannot create file at ${uri.fsPath}: Parent path exists but is not a directory.`); + } + } catch (error) { + // Parent doesn't exist, create it + if (error instanceof Error && (error.message.includes('not found') || error.message.includes('ENOENT'))) { + try { + await fileService.createFolder(parentDir); + } catch (createError) { + // Parent directory might already exist (race condition), or creation failed + // Verify it exists now + try { + const verifyStat = await fileService.stat(parentDir); + if (!verifyStat.isDirectory) { + throw new Error(`Cannot create file at ${uri.fsPath}: Parent path exists but is not a directory.`); + } + } catch (verifyError) { + const createErrorMsg = createError instanceof Error ? createError.message : String(createError); + throw new Error(`Cannot create file at ${uri.fsPath}: Failed to create parent directory. Error: ${createErrorMsg}`); + } + } + } else { + throw error; + } + } + } + + // Create the file + await fileService.createFile(uri); + + // Verify file was actually created and is readable + try { + const stat = await fileService.stat(uri); + if (stat.isDirectory) { + throw new Error(`File creation failed: A directory was created instead of a file at ${uri.fsPath}.`); + } + } catch (error) { + if (error instanceof Error && error.message.includes('directory')) { + throw error; + } + throw new Error(`File was not created successfully at ${uri.fsPath}. Error: ${error}`); + } } - return { result: {} } + return { result: {} }; }, delete_file_or_folder: async ({ uri, isRecursive }) => { - await fileService.del(uri, { recursive: isRecursive }) - return { result: {} } + // Validate file/folder exists before attempting deletion + try { + const exists = await fileService.exists(uri); + if (!exists) { + throw new Error(`Cannot delete: File or folder at ${uri.fsPath} does not exist.`); + } + + // Additional safety check: verify it's actually a file or directory + const stat = await fileService.stat(uri); + if (stat.isDirectory && !isRecursive) { + // Check if directory is empty by resolving it + try { + const resolved = await fileService.resolve(uri); + if (resolved.children && resolved.children.length > 0) { + throw new Error(`Cannot delete directory ${uri.fsPath}: Directory is not empty. Use is_recursive=true to delete non-empty directories.`); + } + } catch (readError) { + // If we can't read directory, it might be a permission issue + if (readError instanceof Error && !readError.message.includes('not empty')) { + throw new Error(`Cannot delete directory ${uri.fsPath}: ${readError.message}`); + } + throw readError; + } + } + + await fileService.del(uri, { recursive: isRecursive }); + + // Verify deletion was successful + const stillExists = await fileService.exists(uri); + if (stillExists) { + throw new Error(`Deletion may have failed: File or folder at ${uri.fsPath} still exists after deletion attempt.`); + } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to delete file or folder at ${uri.fsPath}: ${errorMsg}`); + } + return { result: {} }; }, rewrite_file: async ({ uri, newContent }) => { - await cortexideModelService.initializeModel(uri) + await cortexideModelService.initializeModel(uri); if (this.commandBarService.getStreamState(uri) === 'streaming') { - throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`) + throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`); } - await editCodeService.callBeforeApplyOrEdit(uri) - editCodeService.instantlyRewriteFile({ uri, newContent }) + await editCodeService.callBeforeApplyOrEdit(uri); + editCodeService.instantlyRewriteFile({ uri, newContent }); // at end, get lint errors const lintErrorsPromise = Promise.resolve().then(async () => { - await timeout(2000) - const { lintErrors } = this._getLintErrors(uri) - return { lintErrors } - }) - return { result: lintErrorsPromise } + await timeout(2000); + const { lintErrors } = this._getLintErrors(uri); + return { lintErrors }; + }); + return { result: lintErrorsPromise }; }, edit_file: async ({ uri, searchReplaceBlocks }) => { - await cortexideModelService.initializeModel(uri) + await cortexideModelService.initializeModel(uri); if (this.commandBarService.getStreamState(uri) === 'streaming') { - throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`) + throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`); } - await editCodeService.callBeforeApplyOrEdit(uri) - editCodeService.instantlyApplySearchReplaceBlocks({ uri, searchReplaceBlocks }) + await editCodeService.callBeforeApplyOrEdit(uri); + editCodeService.instantlyApplySearchReplaceBlocks({ uri, searchReplaceBlocks }); // at end, get lint errors const lintErrorsPromise = Promise.resolve().then(async () => { - await timeout(2000) - const { lintErrors } = this._getLintErrors(uri) - return { lintErrors } - }) + await timeout(2000); + const { lintErrors } = this._getLintErrors(uri); + return { lintErrors }; + }); - return { result: lintErrorsPromise } + return { result: lintErrorsPromise }; }, // --- run_command: async ({ command, cwd, terminalId }) => { @@ -609,12 +862,23 @@ export class ToolsService implements IToolsService { } else if (dangerLevel === 'medium') { this.notificationService.info(`⚠️ Potentially risky command: ${command}\nReview before execution.`); } - const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'temporary', cwd, terminalId }) - return { result: resPromise, interruptTool: interrupt } + const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'temporary', cwd, terminalId }); + return { result: resPromise, interruptTool: interrupt }; }, run_nl_command: async ({ nlInput, cwd, terminalId }) => { // Parse natural language to shell command - const parsed = await this.nlShellParserService.parseNLToShell(nlInput, cwd, CancellationToken.None); + let parsed; + try { + parsed = await this.nlShellParserService.parseNLToShell(nlInput, cwd, CancellationToken.None); + } catch (parseError) { + const errorMsg = parseError instanceof Error ? parseError.message : String(parseError); + throw new Error(`Failed to parse natural language command: ${errorMsg}. Please provide a clearer command description.`); + } + + // Validate parsed command is not empty + if (!parsed.command || parsed.command.trim().length === 0) { + throw new Error(`Failed to parse natural language command: The parser did not generate a valid command from "${nlInput}". Please provide a clearer command description.`); + } // Check for dangerous commands using existing detection const dangerLevel = this._detectCommandDanger(parsed.command); @@ -653,17 +917,17 @@ export class ToolsService implements IToolsService { } else if (dangerLevel === 'medium') { this.notificationService.info(`⚠️ Potentially risky command: ${command}\nReview before execution.`); } - const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'persistent', persistentTerminalId }) - return { result: resPromise, interruptTool: interrupt } + const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'persistent', persistentTerminalId }); + return { result: resPromise, interruptTool: interrupt }; }, open_persistent_terminal: async ({ cwd }) => { - const persistentTerminalId = await this.terminalToolService.createPersistentTerminal({ cwd }) - return { result: { persistentTerminalId } } + const persistentTerminalId = await this.terminalToolService.createPersistentTerminal({ cwd }); + return { result: { persistentTerminalId } }; }, kill_persistent_terminal: async ({ persistentTerminalId }) => { // Close the background terminal by sending exit - await this.terminalToolService.killPersistentTerminal(persistentTerminalId) - return { result: {} } + await this.terminalToolService.killPersistentTerminal(persistentTerminalId); + return { result: {} }; }, // --- @@ -672,7 +936,8 @@ export class ToolsService implements IToolsService { // Check offline/privacy mode (centralized gate) this._offlineGate.ensureNotOfflineOrPrivacy('Web search', false); - const cacheKey = `search:${query}:${k}`; + // Include refresh in cache key to ensure uniqueness + const cacheKey = `search:${query}:${k}:${refresh ? 'refresh' : 'cached'}`; const cached = this._webSearchCache.get(cacheKey); if (!refresh && cached && Date.now() - cached.timestamp < this._cacheTTL) { return { result: { results: cached.results } }; @@ -684,7 +949,7 @@ export class ToolsService implements IToolsService { // Try multiple search methods with retries // Methods that use webContentExtractorService run in main process and bypass CORS - const searchMethods: Array<{ name: string, method: () => Promise> }> = [ + const searchMethods: Array<{ name: string; method: () => Promise> }> = [ // Method 1: DuckDuckGo Instant Answer API (fast, direct API - may hit CORS but worth trying first) { name: 'DuckDuckGo Instant Answer API', @@ -697,8 +962,8 @@ export class ToolsService implements IToolsService { timeout: 10000, }, CancellationToken.None); - const json = await asJson(response); - const results: Array<{ title: string, snippet: string, url: string }> = []; + const json = await asJson(response); + const results: Array<{ title: string; snippet: string; url: string }> = []; if (json?.AbstractText) { results.push({ @@ -749,11 +1014,11 @@ export class ToolsService implements IToolsService { } const content = extracted[0].result; - const results: Array<{ title: string, snippet: string, url: string }> = []; + const results: Array<{ title: string; snippet: string; url: string }> = []; // Helper function to extract real URL from DuckDuckGo redirect const extractRealUrl = (url: string): string | null => { - if (!url || !url.startsWith('http')) return null; + if (!url || !url.startsWith('http')) { return null; } // Check if it's a DuckDuckGo redirect URL if (url.includes('duckduckgo.com/l/')) { @@ -782,7 +1047,7 @@ export class ToolsService implements IToolsService { // Strategy 1: Parse markdown links [text](url) - most reliable const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; - const markdownLinks: Array<{ url: string, title: string, index: number }> = []; + const markdownLinks: Array<{ url: string; title: string; index: number }> = []; let match; markdownLinkRegex.lastIndex = 0; @@ -791,11 +1056,11 @@ export class ToolsService implements IToolsService { const title = match[1].trim(); // Skip empty titles or URLs - if (!title || !rawUrl) continue; + if (!title || !rawUrl) { continue; } // Extract real URL (handles DuckDuckGo redirects) const realUrl = extractRealUrl(rawUrl); - if (!realUrl) continue; + if (!realUrl) { continue; } // Filter out DuckDuckGo internal links and invalid URLs if (realUrl.startsWith('http://') || realUrl.startsWith('https://')) { @@ -843,7 +1108,7 @@ export class ToolsService implements IToolsService { if (results.length < maxResults) { const existingUrls = new Set(results.map(r => r.url)); const urlRegex = /https?:\/\/[^\s<>"'\n\r\)]+/gi; - const urlMatches: Array<{ url: string, index: number }> = []; + const urlMatches: Array<{ url: string; index: number }> = []; urlRegex.lastIndex = 0; const needed = maxResults - results.length; @@ -852,7 +1117,7 @@ export class ToolsService implements IToolsService { // Extract real URL from DuckDuckGo redirect if needed const realUrl = extractRealUrl(rawUrl); - if (!realUrl) continue; + if (!realUrl) { continue; } if (realUrl.length > 10 && realUrl.length < 500 && !realUrl.includes('duckduckgo.com') && @@ -998,15 +1263,27 @@ export class ToolsService implements IToolsService { } // Fallback: fetch and extract text manually (always available as backup) - const response = await this.requestService.request({ - type: 'GET', - url, - timeout: 15000, - }, CancellationToken.None); + let response; + try { + response = await this.requestService.request({ + type: 'GET', + url, + timeout: 15000, + }, CancellationToken.None); + } catch (requestError) { + const errorMsg = requestError instanceof Error ? requestError.message : String(requestError); + if (errorMsg.includes('timeout') || errorMsg.includes('TIMEOUT')) { + throw new Error(`Request timeout: Failed to fetch ${url} within 15 seconds. The server may be slow or unresponsive.`); + } + if (errorMsg.includes('CORS') || errorMsg.includes('network')) { + throw new Error(`Network error: Failed to fetch ${url}. This may be due to CORS restrictions or network connectivity issues.`); + } + throw new Error(`Failed to fetch ${url}: ${errorMsg}`); + } const html = await asTextOrError(response); if (!html) { - throw new Error('Failed to fetch page content'); + throw new Error(`Failed to fetch page content from ${url}. The server returned an empty response.`); } // Simple HTML to text extraction @@ -1034,113 +1311,114 @@ export class ToolsService implements IToolsService { throw new Error(`Failed to browse URL ${url}: ${errorMessage}. Please check the URL and your internet connection.`); } }, - } + }; - const nextPageStr = (hasNextPage: boolean) => hasNextPage ? '\n\n(more on next page...)' : '' + const nextPageStr = (hasNextPage: boolean) => hasNextPage ? '\n\n(more on next page...)' : ''; const stringifyLintErrors = (lintErrors: LintErrorItem[]) => { return lintErrors .map((e, i) => `Error ${i + 1}:\nLines Affected: ${e.startLineNumber}-${e.endLineNumber}\nError message:${e.message}`) .join('\n\n') - .substring(0, MAX_FILE_CHARS_PAGE) - } + .substring(0, MAX_FILE_CHARS_PAGE); + }; // given to the LLM after the call for successful tool calls this.stringOfResult = { read_file: (params, result) => { - return `${params.uri.fsPath}\n\`\`\`\n${result.fileContents}\n\`\`\`${nextPageStr(result.hasNextPage)}${result.hasNextPage ? `\nMore info because truncated: this file has ${result.totalNumLines} lines, or ${result.totalFileLen} characters.` : ''}` + return `${params.uri.fsPath}\n\`\`\`\n${result.fileContents}\n\`\`\`${nextPageStr(result.hasNextPage)}${result.hasNextPage ? `\nMore info because truncated: this file has ${result.totalNumLines} lines, or ${result.totalFileLen} characters.` : ''}`; }, ls_dir: (params, result) => { - const dirTreeStr = stringifyDirectoryTree1Deep(params, result) - return dirTreeStr // + nextPageStr(result.hasNextPage) // already handles num results remaining + const dirTreeStr = stringifyDirectoryTree1Deep(params, result); + return dirTreeStr; // + nextPageStr(result.hasNextPage) // already handles num results remaining }, get_dir_tree: (params, result) => { - return result.str + return result.str; }, search_pathnames_only: (params, result) => { - return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage) + return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage); }, search_for_files: (params, result) => { - return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage) + return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage); }, search_in_file: (params, result) => { - const { model } = cortexideModelService.getModel(params.uri) - if (!model) return '' + const { model } = cortexideModelService.getModel(params.uri); + if (!model) { return ''; } const lines = result.lines.map(n => { - const lineContent = model.getValueInRange({ startLineNumber: n, startColumn: 1, endLineNumber: n, endColumn: Number.MAX_SAFE_INTEGER }, EndOfLinePreference.LF) - return `Line ${n}:\n\`\`\`\n${lineContent}\n\`\`\`` + const lineContent = model.getValueInRange({ startLineNumber: n, startColumn: 1, endLineNumber: n, endColumn: Number.MAX_SAFE_INTEGER }, EndOfLinePreference.LF); + return `Line ${n}:\n\`\`\`\n${lineContent}\n\`\`\``; }).join('\n\n'); return lines; }, read_lint_errors: (params, result) => { return result.lintErrors ? stringifyLintErrors(result.lintErrors) - : 'No lint errors found.' + : 'No lint errors found.'; }, // --- create_file_or_folder: (params, result) => { - return `URI ${params.uri.fsPath} successfully created.` + const filePath = params.uri.fsPath; + return `File successfully created at: ${filePath}\n\nTo open this file, use the exact path: ${filePath}`; }, delete_file_or_folder: (params, result) => { - return `URI ${params.uri.fsPath} successfully deleted.` + return `URI ${params.uri.fsPath} successfully deleted.`; }, edit_file: (params, result) => { const lintErrsString = ( this.cortexideSettingsService.state.globalSettings.includeToolLintErrors ? (result.lintErrors ? ` Lint errors found after change:\n${stringifyLintErrors(result.lintErrors)}.\nIf this is related to a change made while calling this tool, you might want to fix the error.` : ` No lint errors found.`) - : '') + : ''); - return `Change successfully made to ${params.uri.fsPath}.${lintErrsString}` + return `Change successfully made to ${params.uri.fsPath}.${lintErrsString}`; }, rewrite_file: (params, result) => { const lintErrsString = ( this.cortexideSettingsService.state.globalSettings.includeToolLintErrors ? (result.lintErrors ? ` Lint errors found after change:\n${stringifyLintErrors(result.lintErrors)}.\nIf this is related to a change made while calling this tool, you might want to fix the error.` : ` No lint errors found.`) - : '') + : ''); - return `Change successfully made to ${params.uri.fsPath}.${lintErrsString}` + return `Change successfully made to ${params.uri.fsPath}.${lintErrsString}`; }, run_command: (params, result) => { - const { resolveReason, result: result_, } = result + const { resolveReason, result: result_, } = result; // success if (resolveReason.type === 'done') { - return `${result_}\n(exit code ${resolveReason.exitCode})` + return `${result_}\n(exit code ${resolveReason.exitCode})`; } // normal command if (resolveReason.type === 'timeout') { - return `${result_}\nTerminal command ran, but was automatically killed by CortexIDE after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity and did not finish successfully. To try with more time, open a persistent terminal and run the command there.` + return `${result_}\nTerminal command ran, but was automatically killed by CortexIDE after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity and did not finish successfully. To try with more time, open a persistent terminal and run the command there.`; } - throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`) + throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`); }, run_nl_command: (params, result) => { - const { resolveReason, result: result_, parsedCommand, explanation } = result + const { resolveReason, result: result_, parsedCommand, explanation } = result; const commandInfo = `Parsed command: \`${parsedCommand}\`\n${explanation}\n\n`; // success if (resolveReason.type === 'done') { - return `${commandInfo}${result_}\n(exit code ${resolveReason.exitCode})` + return `${commandInfo}${result_}\n(exit code ${resolveReason.exitCode})`; } // normal command if (resolveReason.type === 'timeout') { - return `${commandInfo}${result_}\nTerminal command ran, but was automatically killed by CortexIDE after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity and did not finish successfully. To try with more time, open a persistent terminal and run the command there.` + return `${commandInfo}${result_}\nTerminal command ran, but was automatically killed by CortexIDE after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity and did not finish successfully. To try with more time, open a persistent terminal and run the command there.`; } - throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`) + throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`); }, run_persistent_command: (params, result) => { - const { resolveReason, result: result_, } = result - const { persistentTerminalId } = params + const { resolveReason, result: result_, } = result; + const { persistentTerminalId } = params; // success if (resolveReason.type === 'done') { - return `${result_}\n(exit code ${resolveReason.exitCode})` + return `${result_}\n(exit code ${resolveReason.exitCode})`; } // bg command if (resolveReason.type === 'timeout') { - return `${result_}\nTerminal command is running in terminal ${persistentTerminalId}. The given outputs are the results after ${MAX_TERMINAL_BG_COMMAND_TIME} seconds.` + return `${result_}\nTerminal command is running in terminal ${persistentTerminalId}. The given outputs are the results after ${MAX_TERMINAL_BG_COMMAND_TIME} seconds.`; } - throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`) + throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`); }, open_persistent_terminal: (_params, result) => { @@ -1167,7 +1445,7 @@ export class ToolsService implements IToolsService { const metadataStr = result.metadata?.publishedDate ? `Published: ${result.metadata.publishedDate}\n\n` : ''; return `${titleStr}${metadataStr}Content from ${result.url}:\n\n${result.content.substring(0, 10000)}${result.content.length > 10000 ? '\n\n... (content truncated)' : ''}`; }, - } + }; @@ -1257,10 +1535,10 @@ export class ToolsService implements IToolsService { message: (l.severity === MarkerSeverity.Error ? '(error) ' : '(warning) ') + l.message, startLineNumber: l.startLineNumber, endLineNumber: l.endLineNumber, - } satisfies LintErrorItem)) + } satisfies LintErrorItem)); - if (!lintErrors.length) return { lintErrors: null } - return { lintErrors, } + if (!lintErrors.length) { return { lintErrors: null }; } + return { lintErrors, }; } diff --git a/src/vs/workbench/contrib/cortexide/common/helpers/extractCodeFromResult.ts b/src/vs/workbench/contrib/cortexide/common/helpers/extractCodeFromResult.ts index de6402c2fda..d931666f65d 100644 --- a/src/vs/workbench/contrib/cortexide/common/helpers/extractCodeFromResult.ts +++ b/src/vs/workbench/contrib/cortexide/common/helpers/extractCodeFromResult.ts @@ -1,51 +1,50 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -import { DIVIDER, FINAL, ORIGINAL } from '../prompt/prompts.js' +import { DIVIDER, FINAL, ORIGINAL } from '../prompt/prompts.js'; export class SurroundingsRemover { - readonly originalS: string - i: number - j: number + readonly originalS: string; + i: number; + j: number; // string is s[i...j] constructor(s: string) { - this.originalS = s - this.i = 0 - this.j = s.length - 1 + this.originalS = s; + this.i = 0; + this.j = s.length - 1; } value() { - return this.originalS.substring(this.i, this.j + 1) + return this.originalS.substring(this.i, this.j + 1); } // returns whether it removed the whole prefix removePrefix = (prefix: string): boolean => { - let offset = 0 + let offset = 0; // console.log('prefix', prefix, Math.min(this.j, prefix.length - 1)) while (this.i <= this.j && offset <= prefix.length - 1) { - if (this.originalS.charAt(this.i) !== prefix.charAt(offset)) - break - offset += 1 - this.i += 1 + if (this.originalS.charAt(this.i) !== prefix.charAt(offset)) { break; } + offset += 1; + this.i += 1; } - return offset === prefix.length - } + return offset === prefix.length; + }; // // removes suffix from right to left removeSuffix = (suffix: string): boolean => { // e.g. suffix =
, the string is 
hi

= 1; len -= 1) { if (s.endsWith(suffix.substring(0, len))) { // the end of the string equals a prefix - this.j -= len - return len === suffix.length + this.j -= len; + return len === suffix.length; } } - return false - } + return false; + }; // removeSuffix = (suffix: string): boolean => { // let offset = 0 @@ -60,21 +59,19 @@ export class SurroundingsRemover { // either removes all or nothing removeFromStartUntilFullMatch = (until: string, alsoRemoveUntilStr: boolean) => { - const index = this.originalS.indexOf(until, this.i) + const index = this.originalS.indexOf(until, this.i); if (index === -1) { // this.i = this.j + 1 - return false + return false; } // console.log('index', index, until.length) - if (alsoRemoveUntilStr) - this.i = index + until.length - else - this.i = index + if (alsoRemoveUntilStr) { this.i = index + until.length; } + else { this.i = index; } - return true - } + return true; + }; removeCodeBlock = () => { @@ -82,22 +79,22 @@ export class SurroundingsRemover { // 1. ```language\n\n```\n? // 2. ```\n```\n? - const pm = this - const foundCodeBlock = pm.removePrefix('```') - if (!foundCodeBlock) return false + const pm = this; + const foundCodeBlock = pm.removePrefix('```'); + if (!foundCodeBlock) { return false; } - pm.removeFromStartUntilFullMatch('\n', true) // language + pm.removeFromStartUntilFullMatch('\n', true); // language - const j = pm.j - let foundCodeBlockEnd = pm.removeSuffix('```') + const j = pm.j; + let foundCodeBlockEnd = pm.removeSuffix('```'); - if (pm.j === j) foundCodeBlockEnd = pm.removeSuffix('```\n') // if no change, try again with \n after ``` + if (pm.j === j) { foundCodeBlockEnd = pm.removeSuffix('```\n'); } // if no change, try again with \n after ``` - if (!foundCodeBlockEnd) return false + if (!foundCodeBlockEnd) { return false; } - pm.removeSuffix('\n') // remove the newline before ``` - return true - } + pm.removeSuffix('\n'); // remove the newline before ``` + return true; + }; deltaInfo = (recentlyAddedTextLen: number) => { @@ -105,11 +102,11 @@ export class SurroundingsRemover { // ^ i j len // | // recentyAddedIdx - const recentlyAddedIdx = this.originalS.length - recentlyAddedTextLen - const actualDelta = this.originalS.substring(Math.max(this.i, recentlyAddedIdx), this.j + 1) - const ignoredSuffix = this.originalS.substring(Math.max(this.j + 1, recentlyAddedIdx), Infinity) - return [actualDelta, ignoredSuffix] as const - } + const recentlyAddedIdx = this.originalS.length - recentlyAddedTextLen; + const actualDelta = this.originalS.substring(Math.max(this.i, recentlyAddedIdx), this.j + 1); + const ignoredSuffix = this.originalS.substring(Math.max(this.j + 1, recentlyAddedIdx), Infinity); + return [actualDelta, ignoredSuffix] as const; + }; @@ -117,24 +114,24 @@ export class SurroundingsRemover { -export const extractCodeFromRegular = ({ text, recentlyAddedTextLen }: { text: string, recentlyAddedTextLen: number }): [string, string, string] => { +export const extractCodeFromRegular = ({ text, recentlyAddedTextLen }: { text: string; recentlyAddedTextLen: number }): [string, string, string] => { - const pm = new SurroundingsRemover(text) + const pm = new SurroundingsRemover(text); - pm.removeCodeBlock() + pm.removeCodeBlock(); - const s = pm.value() - const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen) + const s = pm.value(); + const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen); - return [s, delta, ignoredSuffix] -} + return [s, delta, ignoredSuffix]; +}; // Ollama has its own FIM, we should not use this if we use that -export const extractCodeFromFIM = ({ text, recentlyAddedTextLen, midTag, }: { text: string, recentlyAddedTextLen: number, midTag: string }): [string, string, string] => { +export const extractCodeFromFIM = ({ text, recentlyAddedTextLen, midTag, }: { text: string; recentlyAddedTextLen: number; midTag: string }): [string, string, string] => { /* ------------- summary of the regex ------------- [optional ` | `` | ```] @@ -146,104 +143,159 @@ export const extractCodeFromFIM = ({ text, recentlyAddedTextLen, midTag, }: { te [optional ` | `` | ```] */ - const pm = new SurroundingsRemover(text) + const pm = new SurroundingsRemover(text); - pm.removeCodeBlock() + pm.removeCodeBlock(); - const foundMid = pm.removePrefix(`<${midTag}>`) + const foundMid = pm.removePrefix(`<${midTag}>`); if (foundMid) { - pm.removeSuffix(`\n`) // sometimes outputs \n - pm.removeSuffix(``) + pm.removeSuffix(`\n`); // sometimes outputs \n + pm.removeSuffix(``); } - const s = pm.value() - const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen) + const s = pm.value(); + const [delta, ignoredSuffix] = pm.deltaInfo(recentlyAddedTextLen); - return [s, delta, ignoredSuffix] -} + return [s, delta, ignoredSuffix]; +}; export type ExtractedSearchReplaceBlock = { - state: 'writingOriginal' | 'writingFinal' | 'done', - orig: string, - final: string, -} + state: 'writingOriginal' | 'writingFinal' | 'done'; + orig: string; + final: string; +}; // JS substring swaps indices, so "ab".substr(1,0) will NOT be '', it will be 'a'! -const voidSubstr = (str: string, start: number, end: number) => end < start ? '' : str.substring(start, end) +const voidSubstr = (str: string, start: number, end: number) => end < start ? '' : str.substring(start, end); export const endsWithAnyPrefixOf = (str: string, anyPrefix: string) => { // for each prefix for (let i = anyPrefix.length; i >= 1; i--) { // i >= 1 because must not be empty string - const prefix = anyPrefix.slice(0, i) - if (str.endsWith(prefix)) return prefix + const prefix = anyPrefix.slice(0, i); + if (str.endsWith(prefix)) { return prefix; } } - return null -} + return null; +}; + +// Helper function to strip markdown code fences from text +// This is used when users paste code snippets with markdown code fences (```) into chat +// via inline editing. The fences need to be removed from ORIGINAL/FINAL text before +// matching against the actual file content. +// +// Handles edge cases: +// - Code blocks with/without language identifiers (```java vs ```) +// - Code blocks with/without trailing newlines (```\n vs ```) +// - Nested code blocks (strips outer ones iteratively) +// - Incomplete code blocks (gracefully handles - returns text unchanged if no closing ```) +// - Empty code blocks (```\n```) +// - Whitespace-only code blocks +// - Text without code fences (returns unchanged) +// +// Examples: +// Input: "```java\ncode\n```" -> Output: "code" +// Input: "```\ncode\n```" -> Output: "code" +// Input: "code" -> Output: "code" (no change) +// Input: "```code" -> Output: "```code" (incomplete, no change) +const stripCodeFences = (text: string): string => { + // Handle null/undefined/empty strings + if (!text || text.trim().length === 0) { + return text; + } + + // Try to remove code blocks from start/end multiple times + // This handles cases where there might be nested or multiple code blocks + // e.g., ```\n```java\ncode\n```\n``` -> code + let result = text; + let previousResult = ''; + let attempts = 0; + const maxAttempts = 5; // Prevent infinite loops (safety check) + + // Keep trying to remove code blocks until no more can be removed + while (attempts < maxAttempts && result !== previousResult) { + previousResult = result; + const remover = new SurroundingsRemover(result); + + // Try to remove code block from start/end + // removeCodeBlock() returns false if no code block is found + if (remover.removeCodeBlock()) { + result = remover.value(); + } else { + // If no code block found, we're done + break; + } + + attempts++; + } + + return result; +}; // guarantees if you keep adding text, array length will strictly grow and state will progress without going back export const extractSearchReplaceBlocks = (str: string) => { - const ORIGINAL_ = ORIGINAL + `\n` - const DIVIDER_ = '\n' + DIVIDER + `\n` + const ORIGINAL_ = ORIGINAL + `\n`; + const DIVIDER_ = '\n' + DIVIDER + `\n`; // logic for FINAL_ is slightly more complicated - should be '\n' + FINAL, but that ignores if the final output is empty - const blocks: ExtractedSearchReplaceBlock[] = [] + const blocks: ExtractedSearchReplaceBlock[] = []; - let i = 0 // search i and beyond (this is done by plain index, not by line number. much simpler this way) + let i = 0; // search i and beyond (this is done by plain index, not by line number. much simpler this way) while (true) { - let origStart = str.indexOf(ORIGINAL_, i) - if (origStart === -1) { return blocks } - origStart += ORIGINAL_.length - i = origStart + let origStart = str.indexOf(ORIGINAL_, i); + if (origStart === -1) { return blocks; } + origStart += ORIGINAL_.length; + i = origStart; // wrote <<<< ORIGINAL\n - let dividerStart = str.indexOf(DIVIDER_, i) + let dividerStart = str.indexOf(DIVIDER_, i); if (dividerStart === -1) { // if didnt find DIVIDER_, either writing originalStr or DIVIDER_ right now - const writingDIVIDERlen = endsWithAnyPrefixOf(str, DIVIDER_)?.length ?? 0 + const writingDIVIDERlen = endsWithAnyPrefixOf(str, DIVIDER_)?.length ?? 0; + const origText = voidSubstr(str, origStart, str.length - writingDIVIDERlen); blocks.push({ - orig: voidSubstr(str, origStart, str.length - writingDIVIDERlen), + orig: stripCodeFences(origText), final: '', state: 'writingOriginal' - }) - return blocks + }); + return blocks; } - const origStrDone = voidSubstr(str, origStart, dividerStart) - dividerStart += DIVIDER_.length - i = dividerStart + const origStrDone = voidSubstr(str, origStart, dividerStart); + dividerStart += DIVIDER_.length; + i = dividerStart; // wrote \n=====\n - const fullFINALStart = str.indexOf(FINAL, i) - const fullFINALStart_ = str.indexOf('\n' + FINAL, i) // go with B if possible, else fallback to A, it's more permissive - const matchedFullFINAL_ = fullFINALStart_ !== -1 && fullFINALStart === fullFINALStart_ + 1 // this logic is really important, otherwise we might look for FINAL_ at a much later part of the string + const fullFINALStart = str.indexOf(FINAL, i); + const fullFINALStart_ = str.indexOf('\n' + FINAL, i); // go with B if possible, else fallback to A, it's more permissive + const matchedFullFINAL_ = fullFINALStart_ !== -1 && fullFINALStart === fullFINALStart_ + 1; // this logic is really important, otherwise we might look for FINAL_ at a much later part of the string - let finalStart = matchedFullFINAL_ ? fullFINALStart_ : fullFINALStart + let finalStart = matchedFullFINAL_ ? fullFINALStart_ : fullFINALStart; if (finalStart === -1) { // if didnt find FINAL_, either writing finalStr or FINAL or FINAL_ right now - const writingFINALlen = endsWithAnyPrefixOf(str, FINAL)?.length ?? 0 - const writingFINALlen_ = endsWithAnyPrefixOf(str, '\n' + FINAL)?.length ?? 0 // this gets priority - const usingWritingFINALlen = Math.max(writingFINALlen, writingFINALlen_) + const writingFINALlen = endsWithAnyPrefixOf(str, FINAL)?.length ?? 0; + const writingFINALlen_ = endsWithAnyPrefixOf(str, '\n' + FINAL)?.length ?? 0; // this gets priority + const usingWritingFINALlen = Math.max(writingFINALlen, writingFINALlen_); + const finalText = voidSubstr(str, dividerStart, str.length - usingWritingFINALlen); blocks.push({ - orig: origStrDone, - final: voidSubstr(str, dividerStart, str.length - usingWritingFINALlen), + orig: stripCodeFences(origStrDone), + final: stripCodeFences(finalText), state: 'writingFinal' - }) - return blocks + }); + return blocks; } - const usingFINAL = matchedFullFINAL_ ? '\n' + FINAL : FINAL - const finalStrDone = voidSubstr(str, dividerStart, finalStart) - finalStart += usingFINAL.length - i = finalStart + const usingFINAL = matchedFullFINAL_ ? '\n' + FINAL : FINAL; + const finalStrDone = voidSubstr(str, dividerStart, finalStart); + finalStart += usingFINAL.length; + i = finalStart; // wrote >>>>> FINAL blocks.push({ - orig: origStrDone, - final: finalStrDone, + orig: stripCodeFences(origStrDone), + final: stripCodeFences(finalStrDone), state: 'done' - }) + }); } -} +}; diff --git a/src/vs/workbench/contrib/cortexide/common/mcpService.ts b/src/vs/workbench/contrib/cortexide/common/mcpService.ts index fd660cfe1a3..b7e04aad0ce 100644 --- a/src/vs/workbench/contrib/cortexide/common/mcpService.ts +++ b/src/vs/workbench/contrib/cortexide/common/mcpService.ts @@ -1,7 +1,7 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import { URI } from '../../../../base/common/uri.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; @@ -22,9 +22,9 @@ import { MCPUserStateOfName } from './cortexideSettingsTypes.js'; type MCPServiceState = { - mcpServerOfName: MCPServerOfName, - error: string | undefined, // global parsing error -} + mcpServerOfName: MCPServerOfName; + error: string | undefined; // global parsing error +}; export interface IMCPService { readonly _serviceBrand: undefined; @@ -36,7 +36,7 @@ export interface IMCPService { getMCPTools(): InternalToolInfo[] | undefined; callMCPTool(toolData: MCPToolCallParams): Promise<{ result: RawMCPToolCall }>; - stringifyResult(result: RawMCPToolCall): string + stringifyResult(result: RawMCPToolCall): string; } export const IMCPService = createDecorator('mcpConfigService'); @@ -44,7 +44,7 @@ export const IMCPService = createDecorator('mcpConfigService'); const MCP_CONFIG_FILE_NAME = 'mcp.json'; -const MCP_CONFIG_SAMPLE = { mcpServers: {} } +const MCP_CONFIG_SAMPLE = { mcpServers: {} }; const MCP_CONFIG_SAMPLE_STRING = JSON.stringify(MCP_CONFIG_SAMPLE, null, 2); @@ -60,13 +60,13 @@ class MCPService extends Disposable implements IMCPService { _serviceBrand: undefined; - private readonly channel: IChannel // MCPChannel + private readonly channel: IChannel; // MCPChannel // list of MCP servers pulled from mcpChannel state: MCPServiceState = { mcpServerOfName: {}, error: undefined, - } + }; // Emitters for server events private readonly _onDidChangeState = new Emitter(); @@ -84,13 +84,13 @@ class MCPService extends Disposable implements IMCPService { @ICortexideSettingsService private readonly cortexideSettingsService: ICortexideSettingsService, ) { super(); - this.channel = this.mainProcessService.getChannel('void-channel-mcp') + this.channel = this.mainProcessService.getChannel('void-channel-mcp'); const onEvent = (e: MCPServerEventResponse) => { // console.log('GOT EVENT', e) - this._setMCPServerState(e.response.name, e.response.newServer) - } + this._setMCPServerState(e.response.name, e.response.newServer); + }; this._register((this.channel.listen('onAdd_server') satisfies Event)(onEvent)); this._register((this.channel.listen('onUpdate_server') satisfies Event)(onEvent)); this._register((this.channel.listen('onDelete_server') satisfies Event)(onEvent)); @@ -124,7 +124,7 @@ class MCPService extends Disposable implements IMCPService { this.state = { ...this.state, mcpServerOfName: remainingServers - } + }; } else { // Add or update the server this.state = { @@ -133,18 +133,18 @@ class MCPService extends Disposable implements IMCPService { ...this.state.mcpServerOfName, [serverName]: newServer } - } + }; } this._onDidChangeState.fire(); - } + }; private readonly _setHasError = async (errMsg: string | undefined) => { this.state = { ...this.state, error: errMsg, - } + }; this._onDidChangeState.fire(); - } + }; // Create the file/directory if it doesn't exist private async _createMCPConfigFile(mcpConfigUri: URI): Promise { @@ -158,10 +158,10 @@ class MCPService extends Disposable implements IMCPService { const mcpConfigUri = await this._getMCPConfigFilePath(); this._register( this.fileService.watch(mcpConfigUri) - ) + ); this._register(this.fileService.onDidFilesChange(async e => { - if (!e.contains(mcpConfigUri)) return + if (!e.contains(mcpConfigUri)) { return; } await this._refreshMCPServers(); })); } @@ -184,7 +184,7 @@ class MCPService extends Disposable implements IMCPService { } public getMCPTools(): InternalToolInfo[] | undefined { - const allTools: InternalToolInfo[] = [] + const allTools: InternalToolInfo[] = []; for (const serverName in this.state.mcpServerOfName) { const server = this.state.mcpServerOfName[serverName]; server.tools?.forEach(tool => { @@ -193,17 +193,17 @@ class MCPService extends Disposable implements IMCPService { params: this._transformInputSchemaToParams(tool.inputSchema), name: tool.name, mcpServerName: serverName, - }) - }) + }); + }); } - if (allTools.length === 0) return undefined - return allTools + if (allTools.length === 0) { return undefined; } + return allTools; } - private _transformInputSchemaToParams(inputSchema?: Record): { [paramName: string]: { description: string } } { + private _transformInputSchemaToParams(inputSchema?: Record): { [paramName: string]: { description: string } } { // Check if inputSchema is valid - if (!inputSchema || !inputSchema.properties) return {}; + if (!inputSchema || !inputSchema.properties) { return {}; } const params: { [paramName: string]: { description: string } } = {}; Object.keys(inputSchema.properties).forEach(paramName => { @@ -218,16 +218,16 @@ class MCPService extends Disposable implements IMCPService { // Add the parameter to the params object params[paramName] = { description: JSON.stringify(propertyValues.description || '', null, 2) || '', - } + }; }); return params; } private async _getMCPConfigFilePath(): Promise { - const appName = this.productService.dataFolderName + const appName = this.productService.dataFolderName; const userHome = await this.pathService.userHome(); - const uri = URI.joinPath(userHome, appName, MCP_CONFIG_FILE_NAME) - return uri + const uri = URI.joinPath(userHome, appName, MCP_CONFIG_FILE_NAME); + return uri; } private async _configFileExists(mcpConfigUri: URI): Promise { @@ -249,10 +249,29 @@ class MCPService extends Disposable implements IMCPService { if (!configFileJson.mcpServers) { throw new Error('Missing mcpServers property'); } - return configFileJson as MCPConfigFileJSON; + + // Convert string URLs to URL objects for each server entry + const processedConfig: MCPConfigFileJSON = { + mcpServers: {} + }; + + for (const [serverName, serverConfig] of Object.entries(configFileJson.mcpServers)) { + // Ensure serverConfig is an object before spreading + if (!serverConfig || typeof serverConfig !== 'object' || Array.isArray(serverConfig)) { + continue; // Skip invalid entries + } + const processedServer = { ...serverConfig } as unknown; + // Convert string URL to URL object if present + if (processedServer.url && typeof processedServer.url === 'string') { + processedServer.url = new URL(processedServer.url); + } + processedConfig.mcpServers[serverName] = processedServer; + } + + return processedConfig; } catch (error) { const fullError = `Error parsing MCP config file: ${error}`; - this._setHasError(fullError) + this._setHasError(fullError); return null; } } @@ -261,22 +280,22 @@ class MCPService extends Disposable implements IMCPService { // Handle server state changes private async _refreshMCPServers(): Promise { - this._setHasError(undefined) + this._setHasError(undefined); const newConfigFileJSON = await this._parseMCPConfigFile(); - if (!newConfigFileJSON) { console.log(`Not setting state: MCP config file not found`); return } - if (!newConfigFileJSON?.mcpServers) { console.log(`Not setting state: MCP config file did not have an 'mcpServers' field`); return } + if (!newConfigFileJSON) { console.log(`Not setting state: MCP config file not found`); return; } + if (!newConfigFileJSON?.mcpServers) { console.log(`Not setting state: MCP config file did not have an 'mcpServers' field`); return; } - const oldConfigFileNames = Object.keys(this.state.mcpServerOfName) - const newConfigFileNames = Object.keys(newConfigFileJSON.mcpServers) + const oldConfigFileNames = Object.keys(this.state.mcpServerOfName); + const newConfigFileNames = Object.keys(newConfigFileJSON.mcpServers); const addedServerNames = newConfigFileNames.filter(serverName => !oldConfigFileNames.includes(serverName)); // in new and not in old const removedServerNames = oldConfigFileNames.filter(serverName => !newConfigFileNames.includes(serverName)); // in old and not in new // set isOn to any new servers in the config - const addedUserStateOfName: MCPUserStateOfName = {} - for (const name of addedServerNames) { addedUserStateOfName[name] = { isOn: true } } + const addedUserStateOfName: MCPUserStateOfName = {}; + for (const name of addedServerNames) { addedUserStateOfName[name] = { isOn: true }; } await this.cortexideSettingsService.addMCPUserStateOfNames(addedUserStateOfName); // delete isOn for any servers that no longer show up in the config @@ -284,9 +303,9 @@ class MCPService extends Disposable implements IMCPService { // set all servers to loading for (const serverName in newConfigFileJSON.mcpServers) { - this._setMCPServerState(serverName, { status: 'loading', tools: [] }) + this._setMCPServerState(serverName, { status: 'loading', tools: [] }); } - const updatedServerNames = Object.keys(newConfigFileJSON.mcpServers).filter(serverName => !addedServerNames.includes(serverName) && !removedServerNames.includes(serverName)) + const updatedServerNames = Object.keys(newConfigFileJSON.mcpServers).filter(serverName => !addedServerNames.includes(serverName) && !removedServerNames.includes(serverName)); this.channel.call('refreshMCPServers', { mcpConfigFileJSON: newConfigFileJSON, @@ -294,38 +313,38 @@ class MCPService extends Disposable implements IMCPService { removedServerNames, updatedServerNames, userStateOfName: this.cortexideSettingsService.state.mcpUserStateOfName, - }) + }); } stringifyResult(result: RawMCPToolCall): string { - let toolResultStr: string + let toolResultStr: string; if (result.event === 'text') { - toolResultStr = result.text + toolResultStr = result.text; } else if (result.event === 'image') { - toolResultStr = `[Image: ${result.image.mimeType}]` + toolResultStr = `[Image: ${result.image.mimeType}]`; } else if (result.event === 'audio') { - toolResultStr = `[Audio content]` + toolResultStr = `[Audio content]`; } else if (result.event === 'resource') { - toolResultStr = `[Resource content]` + toolResultStr = `[Resource content]`; } else { - toolResultStr = JSON.stringify(result) + toolResultStr = JSON.stringify(result); } - return toolResultStr + return toolResultStr; } // toggle MCP server and update isOn in void settings public async toggleServerIsOn(serverName: string, isOn: boolean): Promise { - this._setMCPServerState(serverName, { status: 'loading', tools: [] }) + this._setMCPServerState(serverName, { status: 'loading', tools: [] }); await this.cortexideSettingsService.setMCPServerState(serverName, { isOn }); - this.channel.call('toggleMCPServer', { serverName, isOn }) + this.channel.call('toggleMCPServer', { serverName, isOn }); } public async callMCPTool(toolData: MCPToolCallParams): Promise<{ result: RawMCPToolCall }> { const result = await this.channel.call('callTool', toolData); if (result.event === 'error') { - throw new Error(`Error: ${result.text}`) + throw new Error(`Error: ${result.text}`); } return { result }; } diff --git a/src/vs/workbench/contrib/cortexide/common/mcpServiceTypes.ts b/src/vs/workbench/contrib/cortexide/common/mcpServiceTypes.ts index 81fc716f040..e0b420e6525 100644 --- a/src/vs/workbench/contrib/cortexide/common/mcpServiceTypes.ts +++ b/src/vs/workbench/contrib/cortexide/common/mcpServiceTypes.ts @@ -1,3 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + /** * mcp-response-types.ts * -------------------------------------------------- @@ -9,21 +14,25 @@ * 3. tools/call -> ToolCallResponse * * They are distilled directly from the official MCP + * // allow-any-unicode-next-line * 2025‑03‑26 specification: * • Tools list response examples * • Prompts list response examples * • Tool call response examples * * Use them to get full IntelliSense when working with + * // allow-any-unicode-next-line * @modelcontextprotocol/inspector‑cli responses. */ /* -------------------------------------------------- */ +// allow-any-unicode-next-line /* Core JSON‑RPC envelope */ /* -------------------------------------------------- */ // export interface JsonRpcSuccess { +// // allow-any-unicode-next-line // /** JSON‑RPC version – always '2.0' */ // jsonrpc: '2.0'; // /** Request identifier echoed back by the server */ @@ -48,10 +57,12 @@ export interface MCPTool { /** Unique tool identifier */ name: string; + // allow-any-unicode-next-line /** Human‑readable description */ description?: string; /** JSON schema describing expected arguments */ inputSchema?: Record; + // allow-any-unicode-next-line /** Free‑form annotations describing behaviour, security, etc. */ annotations?: Record; } @@ -93,6 +104,7 @@ export interface MCPTool { // export interface Resource { // uri: string; // mimeType: string; +// // allow-any-unicode-next-line // /** Either plain‑text or base64‑encoded binary data */ // text?: string; // data?: string; @@ -108,6 +120,7 @@ export interface MCPTool { // export interface ToolCallResult { // /** List of content parts (text, images, resources, etc.) */ // content: ToolContent[]; +// // allow-any-unicode-next-line // /** True if the tool itself encountered a domain‑level error */ // isError?: boolean; // } @@ -123,7 +136,8 @@ export interface MCPConfigFileEntryJSON { env?: Record; // URL-based server properties - url?: URL; + url?: string | URL; // Can be string (from JSON) or URL object + type?: 'http' | 'sse'; // Explicit transport type. If not specified, tries HTTP first, then SSE headers?: Record; } @@ -136,16 +150,16 @@ export interface MCPConfigFileJSON { export type MCPServer = { // Command-based server properties - tools: MCPTool[], - status: 'loading' | 'success' | 'offline', - command?: string, - error?: string, + tools: MCPTool[]; + status: 'loading' | 'success' | 'offline'; + command?: string; + error?: string; } | { - tools?: undefined, - status: 'error', - command?: string, - error: string, -} + tools?: undefined; + status: 'error'; + command?: string; + error: string; +}; export interface MCPServerOfName { [serverName: string]: MCPServer; @@ -155,14 +169,14 @@ export type MCPServerEvent = { name: string; prevServer?: MCPServer; newServer?: MCPServer; -} -export type MCPServerEventResponse = { response: MCPServerEvent } +}; +export type MCPServerEventResponse = { response: MCPServerEvent }; export interface MCPConfigFileParseErrorResponse { response: { type: 'config-file-error'; error: string | null; - } + }; } @@ -216,8 +230,8 @@ type MCPToolResponseConstraints = { 'resource': { text?: never; image?: never; - } -} + }; +}; type MCPToolEventResponse = Omit & MCPToolResponseConstraints[T] & { event: T }; @@ -238,5 +252,5 @@ export interface MCPToolCallParams { export const removeMCPToolNamePrefix = (name: string) => { - return name.split('_').slice(1).join('_') -} + return name.split('_').slice(1).join('_'); +}; diff --git a/src/vs/workbench/contrib/cortexide/common/modelCapabilities.ts b/src/vs/workbench/contrib/cortexide/common/modelCapabilities.ts index c331ab34875..2353d889439 100644 --- a/src/vs/workbench/contrib/cortexide/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/cortexide/common/modelCapabilities.ts @@ -1,7 +1,7 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import { FeatureName, ModelSelectionOptions, OverridesOfModel, ProviderName } from './cortexideSettingsTypes.js'; @@ -66,7 +66,7 @@ export const defaultProviderSettings = { endpoint: '', // optionally allow overriding default }, -} as const +} as const; @@ -157,7 +157,7 @@ export const defaultModelsOfProvider = { liteLLM: [], -} as const satisfies Record +} as const satisfies Record; @@ -169,10 +169,10 @@ export type CortexideStaticModelInfo = { // not stateful reservedOutputTokenSpace: number | null; // reserve this much space in the context window for output, defaults to 4096 if null supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated'; // typically you should use 'system-role'. 'separated' means the system message is passed as a separate field (e.g. anthropic) - specialToolFormat?: 'openai-style' | 'anthropic-style' | 'gemini-style', // typically you should use 'openai-style'. null means "can't call tools by default", and asks the LLM to output XML in agent mode + specialToolFormat?: 'openai-style' | 'anthropic-style' | 'gemini-style'; // typically you should use 'openai-style'. null means "can't call tools by default", and asks the LLM to output XML in agent mode supportsFIM: boolean; // whether the model was specifically designed for autocomplete or "FIM" ("fill-in-middle" format) - additionalOpenAIPayload?: { [key: string]: string } // additional payload in the message body for requests that are openai-compatible (ollama, vllm, openai, openrouter, etc) + additionalOpenAIPayload?: { [key: string]: string }; // additional payload in the message body for requests that are openai-compatible (ollama, vllm, openai, openrouter, etc) // reasoning options reasoningCapabilities: false | { @@ -183,7 +183,7 @@ export type CortexideStaticModelInfo = { // not stateful readonly reasoningSlider?: | undefined | { type: 'budget_slider'; min: number; max: number; default: number } // anthropic supports this (reasoning budget) - | { type: 'effort_slider'; values: string[]; default: string } // openai-compatible supports this (reasoning effort) + | { type: 'effort_slider'; values: string[]; default: string }; // openai-compatible supports this (reasoning effort) // if it's open source and specifically outputs think tags, put the think tags here and we'll parse them out (e.g. ollama) readonly openSourceThinkTags?: [string, string]; @@ -198,11 +198,11 @@ export type CortexideStaticModelInfo = { // not stateful output: number; cache_read?: number; cache_write?: number; - } + }; downloadable: false | { - sizeGb: number | 'not-known' - } -} + sizeGb: number | 'not-known'; + }; +}; // if you change the above type, remember to update the Settings link @@ -215,31 +215,31 @@ export const modelOverrideKeys = [ 'supportsFIM', 'reasoningCapabilities', 'additionalOpenAIPayload' -] as const +] as const; export type ModelOverrides = Pick< CortexideStaticModelInfo, (typeof modelOverrideKeys)[number] -> +>; type ProviderReasoningIOSettings = { // include this in payload to get reasoning - input?: { includeInPayload?: (reasoningState: SendableReasoningInfo) => null | { [key: string]: any }, }; + input?: { includeInPayload?: (reasoningState: SendableReasoningInfo) => null | { [key: string]: unknown } }; // nameOfFieldInDelta: reasoning output is in response.choices[0].delta[deltaReasoningField] // needsManualParse: whether we must manually parse out the tags output?: - | { nameOfFieldInDelta?: string, needsManualParse?: undefined, } - | { nameOfFieldInDelta?: undefined, needsManualParse?: true, }; -} + | { nameOfFieldInDelta?: string; needsManualParse?: undefined } + | { nameOfFieldInDelta?: undefined; needsManualParse?: true }; +}; type VoidStaticProviderInfo = { // doesn't change (not stateful) providerReasoningIOSettings?: ProviderReasoningIOSettings; // input/output settings around thinking (allowed to be empty) - only applied if the model supports reasoning output modelOptions: { [key: string]: CortexideStaticModelInfo }; - modelOptionsFallback: (modelName: string, fallbackKnownValues?: Partial) => (CortexideStaticModelInfo & { modelName: string, recognizedModelName: string }) | null; -} + modelOptionsFallback: (modelName: string, fallbackKnownValues?: Partial) => (CortexideStaticModelInfo & { modelName: string; recognizedModelName: string }) | null; +}; @@ -251,7 +251,7 @@ const defaultModelOptions = { supportsSystemMessage: false, supportsFIM: false, reasoningCapabilities: false, -} as const satisfies CortexideStaticModelInfo +} as const satisfies CortexideStaticModelInfo; // TODO!!! double check all context sizes below // TODO!!! add openrouter common models @@ -387,7 +387,7 @@ const openSourceModelOptions_assumingOAICompat = { reasoningCapabilities: false, contextWindow: 1_000_000, reservedOutputTokenSpace: 32_000, } -} as const satisfies { [s: string]: Partial } +} as const satisfies { [s: string]: Partial }; @@ -395,15 +395,15 @@ const openSourceModelOptions_assumingOAICompat = { // keep modelName, but use the fallback's defaults const extensiveModelOptionsFallback: VoidStaticProviderInfo['modelOptionsFallback'] = (modelName, fallbackKnownValues) => { - const lower = modelName.toLowerCase() + const lower = modelName.toLowerCase(); const toFallback = },>(obj: T, recognizedModelName: string & keyof T) - : CortexideStaticModelInfo & { modelName: string, recognizedModelName: string } => { + : CortexideStaticModelInfo & { modelName: string; recognizedModelName: string } => { - const opts = obj[recognizedModelName] + const opts = obj[recognizedModelName]; const supportsSystemMessage = opts.supportsSystemMessage === 'separated' ? 'system-role' - : opts.supportsSystemMessage + : opts.supportsSystemMessage; return { recognizedModelName, @@ -414,65 +414,64 @@ const extensiveModelOptionsFallback: VoidStaticProviderInfo['modelOptionsFallbac downloadable: false, ...fallbackKnownValues }; - } + }; - if (lower.includes('gemini') && (lower.includes('2.5') || lower.includes('2-5'))) return toFallback(geminiModelOptions, 'gemini-2.5-pro-exp-03-25') + if (lower.includes('gemini') && (lower.includes('2.5') || lower.includes('2-5'))) { return toFallback(geminiModelOptions, 'gemini-2.5-pro-exp-03-25'); } - if (lower.includes('claude-3-5') || lower.includes('claude-3.5')) return toFallback(anthropicModelOptions, 'claude-3-5-sonnet-20241022') - if (lower.includes('claude')) return toFallback(anthropicModelOptions, 'claude-3-7-sonnet-20250219') + if (lower.includes('claude-3-5') || lower.includes('claude-3.5')) { return toFallback(anthropicModelOptions, 'claude-3-5-sonnet-20241022'); } + if (lower.includes('claude')) { return toFallback(anthropicModelOptions, 'claude-3-7-sonnet-20250219'); } - if (lower.includes('grok2') || lower.includes('grok2')) return toFallback(xAIModelOptions, 'grok-2') - if (lower.includes('grok')) return toFallback(xAIModelOptions, 'grok-3') + if (lower.includes('grok2') || lower.includes('grok2')) { return toFallback(xAIModelOptions, 'grok-2'); } + if (lower.includes('grok')) { return toFallback(xAIModelOptions, 'grok-3'); } - if (lower.includes('deepseek-r1') || lower.includes('deepseek-reasoner')) return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekR1') - if (lower.includes('deepseek') && lower.includes('v2')) return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekCoderV2') - if (lower.includes('deepseek')) return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekCoderV3') + if (lower.includes('deepseek-r1') || lower.includes('deepseek-reasoner')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekR1'); } + if (lower.includes('deepseek') && lower.includes('v2')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekCoderV2'); } + if (lower.includes('deepseek')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'deepseekCoderV3'); } - if (lower.includes('llama3')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3') - if (lower.includes('llama3.1')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3.1') - if (lower.includes('llama3.2')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3.2') - if (lower.includes('llama3.3')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3.3') - if (lower.includes('llama') || lower.includes('scout')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama4-scout') - if (lower.includes('llama') || lower.includes('maverick')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama4-scout') - if (lower.includes('llama')) return toFallback(openSourceModelOptions_assumingOAICompat, 'llama4-scout') + if (lower.includes('llama3')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3'); } + if (lower.includes('llama3.1')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3.1'); } + if (lower.includes('llama3.2')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3.2'); } + if (lower.includes('llama3.3')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'llama3.3'); } + if (lower.includes('llama') || lower.includes('scout')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'llama4-scout'); } + if (lower.includes('llama') || lower.includes('maverick')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'llama4-scout'); } + if (lower.includes('llama')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'llama4-scout'); } - if (lower.includes('qwen') && lower.includes('2.5') && lower.includes('coder')) return toFallback(openSourceModelOptions_assumingOAICompat, 'qwen2.5coder') - if (lower.includes('qwen') && lower.includes('3')) return toFallback(openSourceModelOptions_assumingOAICompat, 'qwen3') - if (lower.includes('qwen')) return toFallback(openSourceModelOptions_assumingOAICompat, 'qwen3') - if (lower.includes('qwq')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'qwq') } - if (lower.includes('phi4')) return toFallback(openSourceModelOptions_assumingOAICompat, 'phi4') - if (lower.includes('codestral')) return toFallback(openSourceModelOptions_assumingOAICompat, 'codestral') - if (lower.includes('devstral')) return toFallback(openSourceModelOptions_assumingOAICompat, 'devstral') + if (lower.includes('qwen') && lower.includes('2.5') && lower.includes('coder')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'qwen2.5coder'); } + if (lower.includes('qwen') && lower.includes('3')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'qwen3'); } + if (lower.includes('qwen')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'qwen3'); } + if (lower.includes('qwq')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'qwq'); } + if (lower.includes('phi4')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'phi4'); } + if (lower.includes('codestral')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'codestral'); } + if (lower.includes('devstral')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'devstral'); } - if (lower.includes('gemma')) return toFallback(openSourceModelOptions_assumingOAICompat, 'gemma') + if (lower.includes('gemma')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'gemma'); } - if (lower.includes('starcoder2')) return toFallback(openSourceModelOptions_assumingOAICompat, 'starcoder2') + if (lower.includes('starcoder2')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'starcoder2'); } - if (lower.includes('openhands')) return toFallback(openSourceModelOptions_assumingOAICompat, 'openhands-lm-32b') // max output uncler + if (lower.includes('openhands')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'openhands-lm-32b'); } // max output uncler - if (lower.includes('quasar') || lower.includes('quaser')) return toFallback(openSourceModelOptions_assumingOAICompat, 'quasar') + if (lower.includes('quasar') || lower.includes('quaser')) { return toFallback(openSourceModelOptions_assumingOAICompat, 'quasar'); } - if (lower.includes('gpt') && lower.includes('mini') && (lower.includes('5') || lower.includes('5.0'))) return toFallback(openAIModelOptions, 'gpt-5-mini') - if (lower.includes('gpt') && (lower.includes('5') || lower.includes('5.0'))) return toFallback(openAIModelOptions, 'gpt-5') - if (lower.includes('gpt') && lower.includes('mini') && (lower.includes('4.1') || lower.includes('4-1'))) return toFallback(openAIModelOptions, 'gpt-4.1-mini') - if (lower.includes('gpt') && lower.includes('nano') && (lower.includes('4.1') || lower.includes('4-1'))) return toFallback(openAIModelOptions, 'gpt-4.1-nano') - if (lower.includes('gpt') && (lower.includes('4.1') || lower.includes('4-1'))) return toFallback(openAIModelOptions, 'gpt-4.1') + if (lower.includes('gpt') && lower.includes('mini') && (lower.includes('5') || lower.includes('5.0'))) { return toFallback(openAIModelOptions, 'gpt-5-mini'); } + if (lower.includes('gpt') && (lower.includes('5') || lower.includes('5.0'))) { return toFallback(openAIModelOptions, 'gpt-5'); } + if (lower.includes('gpt') && lower.includes('mini') && (lower.includes('4.1') || lower.includes('4-1'))) { return toFallback(openAIModelOptions, 'gpt-4.1-mini'); } + if (lower.includes('gpt') && lower.includes('nano') && (lower.includes('4.1') || lower.includes('4-1'))) { return toFallback(openAIModelOptions, 'gpt-4.1-nano'); } + if (lower.includes('gpt') && (lower.includes('4.1') || lower.includes('4-1'))) { return toFallback(openAIModelOptions, 'gpt-4.1'); } - if (lower.includes('4o') && lower.includes('mini')) return toFallback(openAIModelOptions, 'gpt-4o-mini') - if (lower.includes('4o')) return toFallback(openAIModelOptions, 'gpt-4o') + if (lower.includes('4o') && lower.includes('mini')) { return toFallback(openAIModelOptions, 'gpt-4o-mini'); } + if (lower.includes('4o')) { return toFallback(openAIModelOptions, 'gpt-4o'); } - if (lower.includes('o1') && lower.includes('mini')) return toFallback(openAIModelOptions, 'o1-mini') - if (lower.includes('o1')) return toFallback(openAIModelOptions, 'o1') - if (lower.includes('o3') && lower.includes('mini')) return toFallback(openAIModelOptions, 'o3-mini') - if (lower.includes('o3')) return toFallback(openAIModelOptions, 'o3') - if (lower.includes('o4') && lower.includes('mini')) return toFallback(openAIModelOptions, 'o4-mini') + if (lower.includes('o1') && lower.includes('mini')) { return toFallback(openAIModelOptions, 'o1-mini'); } + if (lower.includes('o1')) { return toFallback(openAIModelOptions, 'o1'); } + if (lower.includes('o3') && lower.includes('mini')) { return toFallback(openAIModelOptions, 'o3-mini'); } + if (lower.includes('o3')) { return toFallback(openAIModelOptions, 'o3'); } + if (lower.includes('o4') && lower.includes('mini')) { return toFallback(openAIModelOptions, 'o4-mini'); } - if (Object.keys(openSourceModelOptions_assumingOAICompat).map(k => k.toLowerCase()).includes(lower)) - return toFallback(openSourceModelOptions_assumingOAICompat, lower as keyof typeof openSourceModelOptions_assumingOAICompat) + if (Object.keys(openSourceModelOptions_assumingOAICompat).map(k => k.toLowerCase()).includes(lower)) { return toFallback(openSourceModelOptions_assumingOAICompat, lower as keyof typeof openSourceModelOptions_assumingOAICompat); } - return null -} + return null; +}; @@ -571,38 +570,38 @@ const anthropicModelOptions = { supportsSystemMessage: 'separated', reasoningCapabilities: false, } -} as const satisfies { [s: string]: CortexideStaticModelInfo } +} as const satisfies { [s: string]: CortexideStaticModelInfo }; const anthropicSettings: VoidStaticProviderInfo = { providerReasoningIOSettings: { input: { includeInPayload: (reasoningInfo) => { - if (!reasoningInfo?.isReasoningEnabled) return null + if (!reasoningInfo?.isReasoningEnabled) { return null; } if (reasoningInfo.type === 'budget_slider_value') { - return { thinking: { type: 'enabled', budget_tokens: reasoningInfo.reasoningBudget } } + return { thinking: { type: 'enabled', budget_tokens: reasoningInfo.reasoningBudget } }; } - return null + return null; } }, }, modelOptions: anthropicModelOptions, modelOptionsFallback: (modelName) => { - const lower = modelName.toLowerCase() - let fallbackName: keyof typeof anthropicModelOptions | null = null - if (lower.includes('claude-4-opus') || lower.includes('claude-opus-4')) fallbackName = 'claude-opus-4-20250514' - if (lower.includes('claude-4-sonnet') || lower.includes('claude-sonnet-4')) fallbackName = 'claude-sonnet-4-20250514' + const lower = modelName.toLowerCase(); + let fallbackName: keyof typeof anthropicModelOptions | null = null; + if (lower.includes('claude-4-opus') || lower.includes('claude-opus-4')) { fallbackName = 'claude-opus-4-20250514'; } + if (lower.includes('claude-4-sonnet') || lower.includes('claude-sonnet-4')) { fallbackName = 'claude-sonnet-4-20250514'; } - if (lower.includes('claude-3-7-sonnet')) fallbackName = 'claude-3-7-sonnet-20250219' - if (lower.includes('claude-3-5-sonnet')) fallbackName = 'claude-3-5-sonnet-20241022' - if (lower.includes('claude-3-5-haiku')) fallbackName = 'claude-3-5-haiku-20241022' - if (lower.includes('claude-3-opus')) fallbackName = 'claude-3-opus-20240229' - if (lower.includes('claude-3-sonnet')) fallbackName = 'claude-3-sonnet-20240229' - if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...anthropicModelOptions[fallbackName] } - return null + if (lower.includes('claude-3-7-sonnet')) { fallbackName = 'claude-3-7-sonnet-20250219'; } + if (lower.includes('claude-3-5-sonnet')) { fallbackName = 'claude-3-5-sonnet-20241022'; } + if (lower.includes('claude-3-5-haiku')) { fallbackName = 'claude-3-5-haiku-20241022'; } + if (lower.includes('claude-3-opus')) { fallbackName = 'claude-3-opus-20240229'; } + if (lower.includes('claude-3-sonnet')) { fallbackName = 'claude-3-sonnet-20240229'; } + if (fallbackName) { return { modelName: fallbackName, recognizedModelName: fallbackName, ...anthropicModelOptions[fallbackName] }; } + return null; }, -} +}; // ---------------- OPENAI ---------------- @@ -724,35 +723,35 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing supportsSystemMessage: 'system-role', // ?? reasoningCapabilities: false, }, -} as const satisfies { [s: string]: CortexideStaticModelInfo } +} as const satisfies { [s: string]: CortexideStaticModelInfo }; // https://platform.openai.com/docs/guides/reasoning?api-mode=chat const openAICompatIncludeInPayloadReasoning = (reasoningInfo: SendableReasoningInfo) => { - if (!reasoningInfo?.isReasoningEnabled) return null + if (!reasoningInfo?.isReasoningEnabled) { return null; } if (reasoningInfo.type === 'effort_slider_value') { - return { reasoning_effort: reasoningInfo.reasoningEffort } + return { reasoning_effort: reasoningInfo.reasoningEffort }; } - return null + return null; -} +}; const openAISettings: VoidStaticProviderInfo = { modelOptions: openAIModelOptions, modelOptionsFallback: (modelName) => { - const lower = modelName.toLowerCase() - let fallbackName: keyof typeof openAIModelOptions | null = null - if (lower.includes('gpt-5') || (lower.includes('gpt') && lower.includes('5'))) { fallbackName = 'gpt-5' } - if (lower.includes('o1')) { fallbackName = 'o1' } - if (lower.includes('o3-mini')) { fallbackName = 'o3-mini' } - if (lower.includes('gpt-4o')) { fallbackName = 'gpt-4o' } - if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...openAIModelOptions[fallbackName] } - return null + const lower = modelName.toLowerCase(); + let fallbackName: keyof typeof openAIModelOptions | null = null; + if (lower.includes('gpt-5') || (lower.includes('gpt') && lower.includes('5'))) { fallbackName = 'gpt-5'; } + if (lower.includes('o1')) { fallbackName = 'o1'; } + if (lower.includes('o3-mini')) { fallbackName = 'o3-mini'; } + if (lower.includes('gpt-4o')) { fallbackName = 'gpt-4o'; } + if (fallbackName) { return { modelName: fallbackName, recognizedModelName: fallbackName, ...openAIModelOptions[fallbackName] }; } + return null; }, providerReasoningIOSettings: { input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, }, -} +}; // ---------------- XAI ---------------- const xAIModelOptions = { @@ -809,24 +808,24 @@ const xAIModelOptions = { specialToolFormat: 'openai-style', reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: false, reasoningSlider: { type: 'effort_slider', values: ['low', 'high'], default: 'low' } }, }, -} as const satisfies { [s: string]: CortexideStaticModelInfo } +} as const satisfies { [s: string]: CortexideStaticModelInfo }; const xAISettings: VoidStaticProviderInfo = { modelOptions: xAIModelOptions, modelOptionsFallback: (modelName) => { - const lower = modelName.toLowerCase() - let fallbackName: keyof typeof xAIModelOptions | null = null - if (lower.includes('grok-2')) fallbackName = 'grok-2' - if (lower.includes('grok-3')) fallbackName = 'grok-3' - if (lower.includes('grok')) fallbackName = 'grok-3' - if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...xAIModelOptions[fallbackName] } - return null + const lower = modelName.toLowerCase(); + let fallbackName: keyof typeof xAIModelOptions | null = null; + if (lower.includes('grok-2')) { fallbackName = 'grok-2'; } + if (lower.includes('grok-3')) { fallbackName = 'grok-3'; } + if (lower.includes('grok')) { fallbackName = 'grok-3'; } + if (fallbackName) { return { modelName: fallbackName, recognizedModelName: fallbackName, ...xAIModelOptions[fallbackName] }; } + return null; }, // same implementation as openai providerReasoningIOSettings: { input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, }, -} +}; // ---------------- GEMINI ---------------- @@ -940,12 +939,12 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing specialToolFormat: 'gemini-style', reasoningCapabilities: false, }, -} as const satisfies { [s: string]: CortexideStaticModelInfo } +} as const satisfies { [s: string]: CortexideStaticModelInfo }; const geminiSettings: VoidStaticProviderInfo = { modelOptions: geminiModelOptions, - modelOptionsFallback: (modelName) => { return null }, -} + modelOptionsFallback: (modelName) => { return null; }, +}; @@ -965,18 +964,18 @@ const deepseekModelOptions = { cost: { cache_read: .14, input: .55, output: 2.19, }, downloadable: false, }, -} as const satisfies { [s: string]: CortexideStaticModelInfo } +} as const satisfies { [s: string]: CortexideStaticModelInfo }; const deepseekSettings: VoidStaticProviderInfo = { modelOptions: deepseekModelOptions, - modelOptionsFallback: (modelName) => { return null }, + modelOptionsFallback: (modelName) => { return null; }, providerReasoningIOSettings: { // reasoning: OAICompat + response.choices[0].delta.reasoning_content // https://api-docs.deepseek.com/guides/reasoning_model input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, output: { nameOfFieldInDelta: 'reasoning_content' }, }, -} +}; @@ -1055,15 +1054,15 @@ const mistralModelOptions = { // https://mistral.ai/products/la-plateforme#prici supportsSystemMessage: 'system-role', reasoningCapabilities: false, }, -} as const satisfies { [s: string]: CortexideStaticModelInfo } +} as const satisfies { [s: string]: CortexideStaticModelInfo }; const mistralSettings: VoidStaticProviderInfo = { modelOptions: mistralModelOptions, - modelOptionsFallback: (modelName) => { return null }, + modelOptionsFallback: (modelName) => { return null; }, providerReasoningIOSettings: { input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, }, -} +}; // ---------------- GROQ ---------------- @@ -1104,59 +1103,59 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq supportsSystemMessage: 'system-role', reasoningCapabilities: { supportsReasoning: true, canIOReasoning: true, canTurnOffReasoning: false, openSourceThinkTags: ['', ''] }, // we're using reasoning_format:parsed so really don't need to know openSourceThinkTags }, -} as const satisfies { [s: string]: CortexideStaticModelInfo } +} as const satisfies { [s: string]: CortexideStaticModelInfo }; const groqSettings: VoidStaticProviderInfo = { modelOptions: groqModelOptions, - modelOptionsFallback: (modelName) => { return null }, + modelOptionsFallback: (modelName) => { return null; }, providerReasoningIOSettings: { // Must be set to either parsed or hidden when using tool calling https://console.groq.com/docs/reasoning input: { includeInPayload: (reasoningInfo) => { - if (!reasoningInfo?.isReasoningEnabled) return null + if (!reasoningInfo?.isReasoningEnabled) { return null; } if (reasoningInfo.type === 'budget_slider_value') { - return { reasoning_format: 'parsed' } + return { reasoning_format: 'parsed' }; } - return null + return null; } }, output: { nameOfFieldInDelta: 'reasoning' }, }, -} +}; // ---------------- GOOGLE VERTEX ---------------- const googleVertexModelOptions = { -} as const satisfies Record +} as const satisfies Record; const googleVertexSettings: VoidStaticProviderInfo = { modelOptions: googleVertexModelOptions, - modelOptionsFallback: (modelName) => { return null }, + modelOptionsFallback: (modelName) => { return null; }, providerReasoningIOSettings: { input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, }, -} +}; // ---------------- MICROSOFT AZURE ---------------- const microsoftAzureModelOptions = { -} as const satisfies Record +} as const satisfies Record; const microsoftAzureSettings: VoidStaticProviderInfo = { modelOptions: microsoftAzureModelOptions, - modelOptionsFallback: (modelName) => { return null }, + modelOptionsFallback: (modelName) => { return null; }, providerReasoningIOSettings: { input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, }, -} +}; // ---------------- AWS BEDROCK ---------------- const awsBedrockModelOptions = { -} as const satisfies Record +} as const satisfies Record; const awsBedrockSettings: VoidStaticProviderInfo = { modelOptions: awsBedrockModelOptions, - modelOptionsFallback: (modelName) => { return null }, + modelOptionsFallback: (modelName) => { return null; }, providerReasoningIOSettings: { input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, }, -} +}; // ---------------- VLLM, OLLAMA, OPENAICOMPAT (self-hosted / local) ---------------- @@ -1234,9 +1233,9 @@ const ollamaModelOptions = { reasoningCapabilities: false, }, -} as const satisfies Record +} as const satisfies Record; -export const ollamaRecommendedModels = ['qwen2.5-coder:1.5b', 'llama3.1', 'qwq', 'deepseek-r1', 'devstral:latest'] as const satisfies (keyof typeof ollamaModelOptions)[] +export const ollamaRecommendedModels = ['qwen2.5-coder:1.5b', 'llama3.1', 'qwq', 'deepseek-r1', 'devstral:latest'] as const satisfies (keyof typeof ollamaModelOptions)[]; const vLLMSettings: VoidStaticProviderInfo = { @@ -1247,7 +1246,7 @@ const vLLMSettings: VoidStaticProviderInfo = { input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, output: { nameOfFieldInDelta: 'reasoning_content' }, }, -} +}; const lmStudioSettings: VoidStaticProviderInfo = { modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' }, contextWindow: 4_096 }), @@ -1256,7 +1255,7 @@ const lmStudioSettings: VoidStaticProviderInfo = { input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, output: { needsManualParse: true }, }, -} +}; const ollamaSettings: VoidStaticProviderInfo = { modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' } }), @@ -1266,17 +1265,34 @@ const ollamaSettings: VoidStaticProviderInfo = { input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, output: { needsManualParse: true }, }, -} +}; const openaiCompatible: VoidStaticProviderInfo = { - modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName), + modelOptionsFallback: (modelName) => { + const result = extensiveModelOptionsFallback(modelName, { specialToolFormat: 'openai-style' }); + if (result) { + // Ensure OpenAI-compatible models use openai-style tool format + if (!result.specialToolFormat) { + result.specialToolFormat = 'openai-style'; + } + return result; + } + // For unrecognized models, return a default with openai-style tool format + // since they're supposed to be OpenAI-compatible + return { + modelName, + recognizedModelName: modelName, + ...defaultModelOptions, + specialToolFormat: 'openai-style', + }; + }, modelOptions: {}, providerReasoningIOSettings: { // reasoning: we have no idea what endpoint they used, so we can't consistently parse out reasoning input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, output: { nameOfFieldInDelta: 'reasoning_content' }, }, -} +}; const liteLLMSettings: VoidStaticProviderInfo = { // https://docs.litellm.ai/docs/reasoning_content modelOptionsFallback: (modelName) => extensiveModelOptionsFallback(modelName, { downloadable: { sizeGb: 'not-known' } }), @@ -1285,7 +1301,7 @@ const liteLLMSettings: VoidStaticProviderInfo = { // https://docs.litellm.ai/doc input: { includeInPayload: openAICompatIncludeInPayloadReasoning }, output: { nameOfFieldInDelta: 'reasoning_content' }, }, -} +}; // ---------------- OPENROUTER ---------------- @@ -1432,44 +1448,45 @@ const openRouterModelOptions_assumingOpenAICompat = { cost: { input: 0.07, output: 0.16 }, downloadable: false, } -} as const satisfies { [s: string]: CortexideStaticModelInfo } +} as const satisfies { [s: string]: CortexideStaticModelInfo }; const openRouterSettings: VoidStaticProviderInfo = { modelOptions: openRouterModelOptions_assumingOpenAICompat, modelOptionsFallback: (modelName) => { - const res = extensiveModelOptionsFallback(modelName) + const res = extensiveModelOptionsFallback(modelName); // openRouter does not support gemini-style, use openai-style instead if (res?.specialToolFormat === 'gemini-style') { - res.specialToolFormat = 'openai-style' + res.specialToolFormat = 'openai-style'; } - return res + return res; }, providerReasoningIOSettings: { // reasoning: OAICompat + response.choices[0].delta.reasoning : payload should have {include_reasoning: true} https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models input: { // https://openrouter.ai/docs/use-cases/reasoning-tokens includeInPayload: (reasoningInfo) => { - if (!reasoningInfo?.isReasoningEnabled) return null + if (!reasoningInfo?.isReasoningEnabled) { return null; } if (reasoningInfo.type === 'budget_slider_value') { return { reasoning: { max_tokens: reasoningInfo.reasoningBudget } - } + }; } - if (reasoningInfo.type === 'effort_slider_value') + if (reasoningInfo.type === 'effort_slider_value') { return { reasoning: { effort: reasoningInfo.reasoningEffort } - } - return null + }; + } + return null; } }, output: { nameOfFieldInDelta: 'reasoning' }, }, -} +}; @@ -1499,7 +1516,7 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: VoidStaticProvi googleVertex: googleVertexSettings, microsoftAzure: microsoftAzureSettings, awsBedrock: awsBedrockSettings, -} as const +} as const; // ---------------- exports ---------------- @@ -1519,45 +1536,45 @@ export const getModelCapabilities = ( return { modelName, ...defaultModelOptions, isUnrecognizedModel: true }; } - const lowercaseModelName = modelName.toLowerCase() + const lowercaseModelName = modelName.toLowerCase(); - const { modelOptions, modelOptionsFallback } = modelSettingsOfProvider[providerName] + const { modelOptions, modelOptionsFallback } = modelSettingsOfProvider[providerName]; // Get any override settings for this model const overrides = overridesOfModel?.[providerName]?.[modelName]; // search model options object directly first for (const modelName_ in modelOptions) { - const lowercaseModelName_ = modelName_.toLowerCase() + const lowercaseModelName_ = modelName_.toLowerCase(); if (lowercaseModelName === lowercaseModelName_) { return { ...modelOptions[modelName], ...overrides, modelName, recognizedModelName: modelName, isUnrecognizedModel: false }; } } - const result = modelOptionsFallback(modelName) + const result = modelOptionsFallback(modelName); if (result) { return { ...result, ...overrides, modelName: result.modelName, isUnrecognizedModel: false }; } return { modelName, ...defaultModelOptions, ...overrides, isUnrecognizedModel: true }; -} +}; // non-model settings export const getProviderCapabilities = (providerName: ProviderName) => { - const { providerReasoningIOSettings } = modelSettingsOfProvider[providerName] - return { providerReasoningIOSettings } -} + const { providerReasoningIOSettings } = modelSettingsOfProvider[providerName]; + return { providerReasoningIOSettings }; +}; export type SendableReasoningInfo = { - type: 'budget_slider_value', - isReasoningEnabled: true, - reasoningBudget: number, + type: 'budget_slider_value'; + isReasoningEnabled: true; + reasoningBudget: number; } | { - type: 'effort_slider_value', - isReasoningEnabled: true, - reasoningEffort: string, -} | null + type: 'effort_slider_value'; + isReasoningEnabled: true; + reasoningEffort: string; +} | null; @@ -1568,24 +1585,24 @@ export const getIsReasoningEnabledState = ( modelSelectionOptions: ModelSelectionOptions | undefined, overridesOfModel: OverridesOfModel | undefined, ) => { - const { supportsReasoning, canTurnOffReasoning } = getModelCapabilities(providerName, modelName, overridesOfModel).reasoningCapabilities || {} - if (!supportsReasoning) return false + const { supportsReasoning, canTurnOffReasoning } = getModelCapabilities(providerName, modelName, overridesOfModel).reasoningCapabilities || {}; + if (!supportsReasoning) { return false; } // default to enabled if can't turn off, or if the featureName is Chat. - const defaultEnabledVal = featureName === 'Chat' || !canTurnOffReasoning + const defaultEnabledVal = featureName === 'Chat' || !canTurnOffReasoning; - const isReasoningEnabled = modelSelectionOptions?.reasoningEnabled ?? defaultEnabledVal - return isReasoningEnabled -} + const isReasoningEnabled = modelSelectionOptions?.reasoningEnabled ?? defaultEnabledVal; + return isReasoningEnabled; +}; -export const getReservedOutputTokenSpace = (providerName: ProviderName, modelName: string, opts: { isReasoningEnabled: boolean, overridesOfModel: OverridesOfModel | undefined }) => { +export const getReservedOutputTokenSpace = (providerName: ProviderName, modelName: string, opts: { isReasoningEnabled: boolean; overridesOfModel: OverridesOfModel | undefined }) => { const { reasoningCapabilities, reservedOutputTokenSpace, - } = getModelCapabilities(providerName, modelName, opts.overridesOfModel) - return opts.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningReservedOutputTokenSpace : reservedOutputTokenSpace -} + } = getModelCapabilities(providerName, modelName, opts.overridesOfModel); + return opts.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningReservedOutputTokenSpace : reservedOutputTokenSpace; +}; // used to force reasoning state (complex) into something simple we can just read from when sending a message export const getSendableReasoningInfo = ( @@ -1596,21 +1613,21 @@ export const getSendableReasoningInfo = ( overridesOfModel: OverridesOfModel | undefined, ): SendableReasoningInfo => { - const { reasoningSlider: reasoningBudgetSlider } = getModelCapabilities(providerName, modelName, overridesOfModel).reasoningCapabilities || {} - const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel) - if (!isReasoningEnabled) return null + const { reasoningSlider: reasoningBudgetSlider } = getModelCapabilities(providerName, modelName, overridesOfModel).reasoningCapabilities || {}; + const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel); + if (!isReasoningEnabled) { return null; } // check for reasoning budget - const reasoningBudget = reasoningBudgetSlider?.type === 'budget_slider' ? modelSelectionOptions?.reasoningBudget ?? reasoningBudgetSlider?.default : undefined + const reasoningBudget = reasoningBudgetSlider?.type === 'budget_slider' ? modelSelectionOptions?.reasoningBudget ?? reasoningBudgetSlider?.default : undefined; if (reasoningBudget) { - return { type: 'budget_slider_value', isReasoningEnabled: isReasoningEnabled, reasoningBudget: reasoningBudget } + return { type: 'budget_slider_value', isReasoningEnabled: isReasoningEnabled, reasoningBudget: reasoningBudget }; } // check for reasoning effort - const reasoningEffort = reasoningBudgetSlider?.type === 'effort_slider' ? modelSelectionOptions?.reasoningEffort ?? reasoningBudgetSlider?.default : undefined + const reasoningEffort = reasoningBudgetSlider?.type === 'effort_slider' ? modelSelectionOptions?.reasoningEffort ?? reasoningBudgetSlider?.default : undefined; if (reasoningEffort) { - return { type: 'effort_slider_value', isReasoningEnabled: isReasoningEnabled, reasoningEffort: reasoningEffort } + return { type: 'effort_slider_value', isReasoningEnabled: isReasoningEnabled, reasoningEffort: reasoningEffort }; } - return null -} + return null; +}; diff --git a/src/vs/workbench/contrib/cortexide/common/modelRouter.ts b/src/vs/workbench/contrib/cortexide/common/modelRouter.ts index 210a330d86a..3d84905d7c6 100644 --- a/src/vs/workbench/contrib/cortexide/common/modelRouter.ts +++ b/src/vs/workbench/contrib/cortexide/common/modelRouter.ts @@ -1,12 +1,11 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -import { ProviderName, ModelSelection } from './cortexideSettingsTypes.js'; +import { ProviderName, ModelSelection, localProviderNames } from './cortexideSettingsTypes.js'; import { getModelCapabilities, CortexideStaticModelInfo } from './modelCapabilities.js'; import { ICortexideSettingsService } from './cortexideSettingsService.js'; -import { localProviderNames } from './cortexideSettingsTypes.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; @@ -106,7 +105,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR */ private getCachedCapabilities( modelSelection: ModelSelection, - settingsState: any + settingsState: unknown ): ReturnType { const key = `${modelSelection.providerName}:${modelSelection.modelName}:${this.capabilityCacheVersion}`; if (this.capabilityCache.has(key)) { @@ -197,23 +196,37 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR // Get all available models const availableModels = this.getAvailableModels(settingsState); + // Validate that we have at least one model available + if (availableModels.length === 0) { + console.error('[ModelRouter] ERROR: No models available for routing. All providers may be disabled or misconfigured.'); + // Return a decision that indicates failure (will be caught by caller) + return { + modelSelection: { providerName: 'auto', modelName: 'auto' }, // Placeholder to indicate failure + confidence: 0.0, + reasoning: 'No models available. Please configure at least one model provider in settings.', + qualityTier: 'abstain', + shouldAbstain: true, + abstainReason: 'No models available. Please configure at least one model provider in settings.', + }; + } + // Check if online models are available (for codebase questions, we strongly prefer online models) const hasOnlineModels = availableModels.some(m => { - if (m.providerName === 'auto') return false; + if (m.providerName === 'auto') { return false; } return !(localProviderNames as readonly ProviderName[]).includes(m.providerName as ProviderName); }); // Debug: Log available models for codebase questions const isCodebaseQuestionCheck = (context.requiresComplexReasoning && context.taskType === 'code' && !context.hasCode) || - (context.contextSize && context.contextSize > 15000) || - (context.taskType === 'code' && context.isLongMessage && !context.hasCode); + (context.contextSize && context.contextSize > 15000) || + (context.taskType === 'code' && context.isLongMessage && !context.hasCode); if (isCodebaseQuestionCheck) { const onlineModels = availableModels.filter(m => { - if (m.providerName === 'auto') return false; + if (m.providerName === 'auto') { return false; } return !(localProviderNames as readonly ProviderName[]).includes(m.providerName as ProviderName); }); const localModels = availableModels.filter(m => { - if (m.providerName === 'auto') return false; + if (m.providerName === 'auto') { return false; } return (localProviderNames as readonly ProviderName[]).includes(m.providerName as ProviderName); }); console.log('[ModelRouter] Codebase question detected:', { @@ -232,7 +245,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR if (context.isSimpleQuestion && !context.hasImages && !context.hasPDFs && !context.requiresComplexReasoning && !context.contextSize) { // Quick heuristic: prefer fast online models for simple questions const fastModels = availableModels.filter(m => { - if (m.providerName === 'auto') return false; + if (m.providerName === 'auto') { return false; } const name = m.modelName.toLowerCase(); return name.includes('mini') || name.includes('haiku') || name.includes('flash') || name.includes('nano') || name.includes('3.5-turbo'); }); @@ -255,7 +268,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR // Ultra-fast path: Vision tasks → vision model (skip all scoring) if ((context.taskType === 'vision' || context.hasImages) && !context.requiresComplexReasoning && !context.contextSize) { const visionModels = availableModels.filter(m => { - if (m.providerName === 'auto') return false; + if (m.providerName === 'auto') { return false; } const capabilities = this.getCachedCapabilities(m, settingsState); return this.isVisionCapable(m, capabilities); }); @@ -284,15 +297,15 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR // For codebase questions: STRONGLY prefer online models - filter out local models if online models exist // Detect codebase questions: complex reasoning + code task without code blocks, OR explicit context size requirement const isCodebaseQuestionForFilter = (context.requiresComplexReasoning && context.taskType === 'code' && !context.hasCode) || - (context.contextSize && context.contextSize > 15000) || - (context.taskType === 'code' && context.isLongMessage && !context.hasCode); + (context.contextSize && context.contextSize > 15000) || + (context.taskType === 'code' && context.isLongMessage && !context.hasCode); if (isCodebaseQuestionForFilter && hasOnlineModels) { // For codebase questions with online models available, ONLY consider online models // This ensures we never select local models for codebase questions when better options exist const beforeFilter = candidateModels.length; candidateModels = candidateModels.filter(model => { - if (model.providerName === 'auto') return false; + if (model.providerName === 'auto') { return false; } const isLocal = (localProviderNames as readonly ProviderName[]).includes(model.providerName as ProviderName); return !isLocal; }); @@ -319,7 +332,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR // Filter by vision requirement if (context.taskType === 'vision' || context.hasImages || context.taskType === 'pdf' || context.hasPDFs) { candidateModels = candidateModels.filter(model => { - if (model.providerName === 'auto') return false; + if (model.providerName === 'auto') { return false; } const capabilities = this.getCachedCapabilities(model, settingsState); return this.isVisionCapable(model, capabilities); }); @@ -333,7 +346,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR if (context.contextSize) { const requiredContextSize = context.contextSize; // Narrow type for TypeScript candidateModels = candidateModels.filter(model => { - if (model.providerName === 'auto') return false; + if (model.providerName === 'auto') { return false; } const capabilities = this.getCachedCapabilities(model, settingsState); const availableContext = capabilities.contextWindow - (capabilities.reservedOutputTokenSpace || 4096); return availableContext >= requiredContextSize; @@ -390,8 +403,21 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR } if (scored.length === 0) { + console.error('[ModelRouter] ERROR: No models passed scoring. This should not happen if models are available.'); // Fallback: try local models even if privacy not required - return this.routeToLocalModel(context); + const localFallback = this.routeToLocalModel(context); + // If local fallback also fails, return error indication + if (localFallback.modelSelection.providerName === 'auto' && localFallback.modelSelection.modelName === 'auto') { + return { + modelSelection: { providerName: 'auto', modelName: 'auto' }, // Placeholder to indicate failure + confidence: 0.0, + reasoning: 'No suitable models found. Please configure at least one model provider in settings.', + qualityTier: 'abstain', + shouldAbstain: true, + abstainReason: 'No suitable models found. Please configure at least one model provider in settings.', + }; + } + return localFallback; } const best = scored[0]; @@ -421,9 +447,22 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR // Safety check: ensure we never return 'auto' as a model selection // (This should never happen due to filtering, but add safeguard) if (finalModel.providerName === 'auto' && finalModel.modelName === 'auto') { - // This should never happen, but if it does, fall back to local models - console.error('[ModelRouter] Error: Attempted to return "auto" model selection. Falling back to local model.'); - return this.routeToLocalModel(context); + // This should never happen, but if it does, try to find any available model + console.error('[ModelRouter] ERROR: Attempted to return "auto" model selection. This indicates a bug in routing logic.'); + // Try local models as fallback + const localFallback = this.routeToLocalModel(context); + // If local fallback also returns invalid, return error indication + if (localFallback.modelSelection.providerName === 'auto' && localFallback.modelSelection.modelName === 'auto') { + return { + modelSelection: { providerName: 'auto', modelName: 'auto' }, // Placeholder to indicate failure + confidence: 0.0, + reasoning: 'Routing failed: No valid models found. Please configure at least one model provider.', + qualityTier: 'abstain', + shouldAbstain: true, + abstainReason: 'Routing failed: No valid models found. Please configure at least one model provider.', + }; + } + return localFallback; } // Record routing decision for evaluation @@ -439,8 +478,8 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR // Debug: Warn if local model selected for codebase question when online models available // Detect codebase questions: complex reasoning + code task without code blocks, OR explicit context size requirement const isCodebaseQuestionForDebug = (context.requiresComplexReasoning && context.taskType === 'code' && !context.hasCode) || - (context.contextSize && context.contextSize > 15000) || - (context.taskType === 'code' && context.isLongMessage && !context.hasCode); + (context.contextSize && context.contextSize > 15000) || + (context.taskType === 'code' && context.isLongMessage && !context.hasCode); if (isCodebaseQuestionForDebug) { const isLocal = (localProviderNames as readonly ProviderName[]).includes(finalModel.providerName as ProviderName); @@ -514,7 +553,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR */ private findFastCheapModel( models: ModelSelection[], - settingsState: any + settingsState: unknown ): ModelSelection | null { // Filter out 'auto' provider const validModels = models.filter(m => m.providerName !== 'auto'); @@ -556,9 +595,9 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR // Complex tasks need escalation if (context.requiresComplexReasoning || - context.isMultiStepTask || - (context.contextSize && context.contextSize > 100_000) || - context.isSecurityTask) { + context.isMultiStepTask || + (context.contextSize && context.contextSize > 100_000) || + context.isSecurityTask) { return 'escalate'; } @@ -609,7 +648,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR /** * Get per-model timeout based on task and model characteristics */ - private getModelTimeout(model: ModelSelection, context: TaskContext, settingsState: any): number { + private getModelTimeout(model: ModelSelection, context: TaskContext, settingsState: unknown): number { // Skip 'auto' provider if (model.providerName === 'auto') { return 60_000; // Default timeout @@ -670,11 +709,11 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR parts.push(`Task: ${context.taskType}`); // Add key context - if (context.hasImages) parts.push('with images'); - if (context.hasPDFs) parts.push('with PDFs'); - if (context.hasCode) parts.push('with code'); - if (context.requiresComplexReasoning) parts.push('complex reasoning'); - if (context.contextSize) parts.push(`~${Math.round(context.contextSize / 1000)}k tokens`); + if (context.hasImages) { parts.push('with images'); } + if (context.hasPDFs) { parts.push('with PDFs'); } + if (context.hasCode) { parts.push('with code'); } + if (context.requiresComplexReasoning) { parts.push('complex reasoning'); } + if (context.contextSize) { parts.push(`~${Math.round(context.contextSize / 1000)}k tokens`); } // Add quality tier if (decision.qualityTier) { @@ -693,12 +732,12 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR /** * Get all available models from settings */ - private getAvailableModels(settingsState: any): ModelSelection[] { + private getAvailableModels(settingsState: unknown): ModelSelection[] { const models: ModelSelection[] = []; for (const providerName of Object.keys(settingsState.settingsOfProvider) as ProviderName[]) { const providerSettings = settingsState.settingsOfProvider[providerName]; - if (!providerSettings._didFillInProviderSettings) continue; + if (!providerSettings._didFillInProviderSettings) { continue; } for (const modelInfo of providerSettings.models) { if (!modelInfo.isHidden) { @@ -720,7 +759,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR private scoreModel( modelSelection: ModelSelection, context: TaskContext, - settingsState: any, + settingsState: unknown, hasOnlineModels: boolean = false ): number { // Skip "auto" - it's not a real model @@ -797,10 +836,10 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR if (isLocal) { // Check if it's a slow local model const isSlowLocalModel = name.includes('13b') || - name.includes('70b') || - name.includes('llama3') && !name.includes('8b') || - name.includes('mistral') && !name.includes('7b') || - name.includes('mixtral'); + name.includes('70b') || + name.includes('llama3') && !name.includes('8b') || + name.includes('mistral') && !name.includes('7b') || + name.includes('mixtral'); if (isSlowLocalModel) { score -= 50; // Very strong penalty for slow local models on simple chat @@ -841,8 +880,8 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR // Codebase questions need large context and good reasoning - prioritize accordingly // Detect codebase questions: complex reasoning + code task without code blocks, OR explicit context size requirement const isCodebaseQuestion = (context.requiresComplexReasoning && context.taskType === 'code' && !context.hasCode) || - (context.contextSize && context.contextSize > 15000) || // High context requirement suggests codebase question - (context.taskType === 'code' && context.isLongMessage && !context.hasCode); + (context.contextSize && context.contextSize > 15000) || // High context requirement suggests codebase question + (context.taskType === 'code' && context.isLongMessage && !context.hasCode); if (isCodebaseQuestion) { // Codebase questions: prioritize large context windows and reasoning @@ -1048,21 +1087,21 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR // Fast local models typically have "fast", "small", "tiny", "1b", "3b", "7b" in name // Slow local models are usually larger: "13b", "70b", "llama3", "mistral", etc. const isFastLocalModel = name.includes('fast') || - name.includes('small') || - name.includes('tiny') || - name.includes('1b') || - name.includes('3b') || - name.includes('7b') && !name.includes('70b') || - name.includes('qwen2.5-0.5b') || - name.includes('qwen2.5-1.5b') || - name.includes('phi-3-mini') || - name.includes('gemma-2b'); + name.includes('small') || + name.includes('tiny') || + name.includes('1b') || + name.includes('3b') || + name.includes('7b') && !name.includes('70b') || + name.includes('qwen2.5-0.5b') || + name.includes('qwen2.5-1.5b') || + name.includes('phi-3-mini') || + name.includes('gemma-2b'); const isSlowLocalModel = name.includes('13b') || - name.includes('70b') || - name.includes('llama3') && !name.includes('8b') || - name.includes('mistral') && !name.includes('7b') || - name.includes('mixtral'); + name.includes('70b') || + name.includes('llama3') && !name.includes('8b') || + name.includes('mistral') && !name.includes('7b') || + name.includes('mixtral'); if (isFastLocalModel) { score += 25; // Bonus for fast local models @@ -1264,7 +1303,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR const provider = modelSelection.providerName.toLowerCase(); // Known vision-capable models - if (provider === 'gemini') return true; // all Gemini models support vision + if (provider === 'gemini') { return true; } // all Gemini models support vision if (provider === 'anthropic') { return name.includes('3.5') || name.includes('3.7') || name.includes('4') || name.includes('opus') || name.includes('sonnet'); } @@ -1288,7 +1327,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR // Collect available local models for (const providerName of localProviderNames) { const providerSettings = settingsState.settingsOfProvider[providerName]; - if (!providerSettings._didFillInProviderSettings) continue; + if (!providerSettings._didFillInProviderSettings) { continue; } for (const modelInfo of providerSettings.models) { if (!modelInfo.isHidden) { @@ -1301,12 +1340,16 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR } if (localModels.length === 0) { + // Don't return a hardcoded model that might not exist - return error indication instead + // This will trigger fallback logic in chatThreadService + console.error('[ModelRouter] ERROR: No local models available for routeToLocalModel'); return { - modelSelection: { providerName: 'ollama', modelName: 'llama3.1' }, // fallback - confidence: 0.3, - reasoning: 'No local models available; using fallback. Please configure a local provider.', - qualityTier: 'standard', - timeoutMs: 30_000, + modelSelection: { providerName: 'auto', modelName: 'auto' }, // Placeholder to indicate failure + confidence: 0.0, + reasoning: 'No local models available. Please configure a local provider.', + qualityTier: 'abstain', + shouldAbstain: true, + abstainReason: 'No local models available. Please configure a local provider.', }; } @@ -1344,7 +1387,7 @@ export class TaskAwareModelRouter extends Disposable implements ITaskAwareModelR modelSelection: ModelSelection, context: TaskContext, score: number, - settingsState: any + settingsState: unknown ): string { // Guard: "auto" is not a real model if (modelSelection.providerName === 'auto' && modelSelection.modelName === 'auto') { diff --git a/src/vs/workbench/contrib/cortexide/common/sendLLMMessageService.ts b/src/vs/workbench/contrib/cortexide/common/sendLLMMessageService.ts index 93c68aa8170..9ffb6569fbc 100644 --- a/src/vs/workbench/contrib/cortexide/common/sendLLMMessageService.ts +++ b/src/vs/workbench/contrib/cortexide/common/sendLLMMessageService.ts @@ -1,7 +1,7 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import { EventLLMMessageOnTextParams, EventLLMMessageOnErrorParams, EventLLMMessageOnFinalMessageParams, ServiceSendLLMMessageParams, MainSendLLMMessageParams, MainLLMMessageAbortParams, ServiceModelListParams, EventModelListOnSuccessParams, EventModelListOnErrorParams, MainModelListParams, OllamaModelResponse, OpenaiCompatibleModelResponse, } from './sendLLMMessageTypes.js'; @@ -33,7 +33,7 @@ export interface ILLMMessageService { export class LLMMessageService extends Disposable implements ILLMMessageService { readonly _serviceBrand: undefined; - private readonly channel: IChannel // LLMMessageChannel + private readonly channel: IChannel; // LLMMessageChannel // sendLLMMessage private readonly llmMessageHooks = { @@ -41,7 +41,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService onFinalMessage: {} as { [eventId: string]: ((params: EventLLMMessageOnFinalMessageParams) => void) }, onError: {} as { [eventId: string]: ((params: EventLLMMessageOnErrorParams) => void) }, onAbort: {} as { [eventId: string]: (() => void) }, // NOT sent over the channel, result is instant when we call .abort() - } + }; // list hooks private readonly listHooks = { @@ -55,10 +55,10 @@ export class LLMMessageService extends Disposable implements ILLMMessageService } } satisfies { [providerName in 'ollama' | 'openAICompat']: { - success: { [eventId: string]: ((params: EventModelListOnSuccessParams) => void) }, - error: { [eventId: string]: ((params: EventModelListOnErrorParams) => void) }, + success: { [eventId: string]: ((params: EventModelListOnSuccessParams) => void) }; + error: { [eventId: string]: ((params: EventModelListOnErrorParams) => void) }; } - } + }; constructor( @IMainProcessService private readonly mainProcessService: IMainProcessService, // used as a renderer (only usable on client side) @@ -67,21 +67,21 @@ export class LLMMessageService extends Disposable implements ILLMMessageService @IMCPService private readonly mcpService: IMCPService, @ISecretDetectionService private readonly secretDetectionService: ISecretDetectionService, ) { - super() + super(); // const service = ProxyChannel.toService(mainProcessService.getChannel('void-channel-sendLLMMessage')); // lets you call it like a service // see llmMessageChannel.ts - this.channel = this.mainProcessService.getChannel('cortexide-channel-llmMessage') + this.channel = this.mainProcessService.getChannel('cortexide-channel-llmMessage'); // .listen sets up an IPC channel and takes a few ms, so we set up listeners immediately and add hooks to them instead // llm this._register((this.channel.listen('onText_sendLLMMessage') satisfies Event)(e => { - this.llmMessageHooks.onText[e.requestId]?.(e) - })) + this.llmMessageHooks.onText[e.requestId]?.(e); + })); this._register((this.channel.listen('onFinalMessage_sendLLMMessage') satisfies Event)(e => { this.llmMessageHooks.onFinalMessage[e.requestId]?.(e); - this._clearChannelHooks(e.requestId) - })) + this._clearChannelHooks(e.requestId); + })); this._register((this.channel.listen('onError_sendLLMMessage') satisfies Event)(e => { this.llmMessageHooks.onError[e.requestId]?.(e); this._clearChannelHooks(e.requestId); @@ -89,24 +89,24 @@ export class LLMMessageService extends Disposable implements ILLMMessageService const config = this.secretDetectionService.getConfig(); if (config.enabled) { const redacted = this.secretDetectionService.redactSecretsInObject(e); - console.error('Error in LLMMessageService:', JSON.stringify(redacted.redacted)) + console.error('Error in LLMMessageService:', JSON.stringify(redacted.redacted)); } else { - console.error('Error in LLMMessageService:', JSON.stringify(e)) + console.error('Error in LLMMessageService:', JSON.stringify(e)); } - })) + })); // .list() this._register((this.channel.listen('onSuccess_list_ollama') satisfies Event>)(e => { - this.listHooks.ollama.success[e.requestId]?.(e) - })) + this.listHooks.ollama.success[e.requestId]?.(e); + })); this._register((this.channel.listen('onError_list_ollama') satisfies Event>)(e => { - this.listHooks.ollama.error[e.requestId]?.(e) - })) + this.listHooks.ollama.error[e.requestId]?.(e); + })); this._register((this.channel.listen('onSuccess_list_openAICompatible') satisfies Event>)(e => { - this.listHooks.openAICompat.success[e.requestId]?.(e) - })) + this.listHooks.openAICompat.success[e.requestId]?.(e); + })); this._register((this.channel.listen('onError_list_openAICompatible') satisfies Event>)(e => { - this.listHooks.openAICompat.error[e.requestId]?.(e) - })) + this.listHooks.openAICompat.error[e.requestId]?.(e); + })); } @@ -115,21 +115,30 @@ export class LLMMessageService extends Disposable implements ILLMMessageService // throw an error if no model/provider selected (this should usually never be reached, the UI should check this first, but might happen in cases like Apply where we haven't built much UI/checks yet, good practice to have check logic on backend) if (modelSelection === null) { - const message = `Please add a provider in CortexIDE Settings.` - onError({ message, fullError: null }) - return null + const message = `Please add a provider in CortexIDE Settings.`; + onError({ message, fullError: null }); + return null; + } + + // CRITICAL: Validate that modelSelection is not the invalid "auto" placeholder + // This should never happen if validation is working correctly, but this is a critical safety check + if (modelSelection && modelSelection.providerName === 'auto' && modelSelection.modelName === 'auto') { + const message = `Invalid model selection: "auto" placeholder detected. This indicates a bug in model routing. Please select a specific model.`; + console.error('[LLMMessageService] CRITICAL: Invalid "auto" modelSelection detected:', modelSelection); + onError({ message, fullError: null }); + return null; } if (params.messagesType === 'chatMessages' && (params.messages?.length ?? 0) === 0) { - const message = `No messages detected.` - onError({ message, fullError: null }) - return null + const message = `No messages detected.`; + onError({ message, fullError: null }); + return null; } // Detect and redact secrets before sending const config = this.secretDetectionService.getConfig(); if (config.enabled && params.messagesType === 'chatMessages' && params.messages) { - let totalMatches: any[] = []; + const totalMatches: unknown[] = []; let hasAnySecrets = false; // Scan all messages for secrets @@ -143,7 +152,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService hasAnySecrets = true; totalMatches.push(...detection.matches); // Redact the message content - (msg as any).content = detection.redactedText; + (msg as unknown).content = detection.redactedText; } } else if (Array.isArray(msg.content)) { // Handle array content (e.g., OpenAI format with images) @@ -153,7 +162,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService if (detection.hasSecrets) { hasAnySecrets = true; totalMatches.push(...detection.matches); - (part as any).text = detection.redactedText; + (part as unknown).text = detection.redactedText; } } } @@ -166,7 +175,7 @@ export class LLMMessageService extends Disposable implements ILLMMessageService if (detection.hasSecrets) { hasAnySecrets = true; totalMatches.push(...detection.matches); - (part as any).text = detection.redactedText; + (part as unknown).text = detection.redactedText; } } } @@ -202,16 +211,16 @@ export class LLMMessageService extends Disposable implements ILLMMessageService } } - const { settingsOfProvider, } = this.cortexideSettingsService.state + const { settingsOfProvider, } = this.cortexideSettingsService.state; - const mcpTools = this.mcpService.getMCPTools() + const mcpTools = this.mcpService.getMCPTools(); // add state for request id const requestId = generateUuid(); - this.llmMessageHooks.onText[requestId] = onText - this.llmMessageHooks.onFinalMessage[requestId] = onFinalMessage - this.llmMessageHooks.onError[requestId] = onError - this.llmMessageHooks.onAbort[requestId] = onAbort // used internally only + this.llmMessageHooks.onText[requestId] = onText; + this.llmMessageHooks.onFinalMessage[requestId] = onFinalMessage; + this.llmMessageHooks.onError[requestId] = onError; + this.llmMessageHooks.onAbort[requestId] = onAbort; // used internally only // params will be stripped of all its functions over the IPC channel this.channel.call('sendLLMMessage', { @@ -222,62 +231,62 @@ export class LLMMessageService extends Disposable implements ILLMMessageService mcpTools, } satisfies MainSendLLMMessageParams); - return requestId + return requestId; } abort(requestId: string) { - this.llmMessageHooks.onAbort[requestId]?.() // calling the abort hook here is instant (doesn't go over a channel) + this.llmMessageHooks.onAbort[requestId]?.(); // calling the abort hook here is instant (doesn't go over a channel) this.channel.call('abort', { requestId } satisfies MainLLMMessageAbortParams); - this._clearChannelHooks(requestId) + this._clearChannelHooks(requestId); } ollamaList = (params: ServiceModelListParams) => { - const { onSuccess, onError, ...proxyParams } = params + const { onSuccess, onError, ...proxyParams } = params; - const { settingsOfProvider } = this.cortexideSettingsService.state + const { settingsOfProvider } = this.cortexideSettingsService.state; // add state for request id const requestId_ = generateUuid(); - this.listHooks.ollama.success[requestId_] = onSuccess - this.listHooks.ollama.error[requestId_] = onError + this.listHooks.ollama.success[requestId_] = onSuccess; + this.listHooks.ollama.error[requestId_] = onError; this.channel.call('ollamaList', { ...proxyParams, settingsOfProvider, providerName: 'ollama', requestId: requestId_, - } satisfies MainModelListParams) - } + } satisfies MainModelListParams); + }; openAICompatibleList = (params: ServiceModelListParams) => { - const { onSuccess, onError, ...proxyParams } = params + const { onSuccess, onError, ...proxyParams } = params; - const { settingsOfProvider } = this.cortexideSettingsService.state + const { settingsOfProvider } = this.cortexideSettingsService.state; // add state for request id const requestId_ = generateUuid(); - this.listHooks.openAICompat.success[requestId_] = onSuccess - this.listHooks.openAICompat.error[requestId_] = onError + this.listHooks.openAICompat.success[requestId_] = onSuccess; + this.listHooks.openAICompat.error[requestId_] = onError; this.channel.call('openAICompatibleList', { ...proxyParams, settingsOfProvider, requestId: requestId_, - } satisfies MainModelListParams) - } + } satisfies MainModelListParams); + }; private _clearChannelHooks(requestId: string) { - delete this.llmMessageHooks.onText[requestId] - delete this.llmMessageHooks.onFinalMessage[requestId] - delete this.llmMessageHooks.onError[requestId] + delete this.llmMessageHooks.onText[requestId]; + delete this.llmMessageHooks.onFinalMessage[requestId]; + delete this.llmMessageHooks.onError[requestId]; - delete this.listHooks.ollama.success[requestId] - delete this.listHooks.ollama.error[requestId] + delete this.listHooks.ollama.success[requestId]; + delete this.listHooks.ollama.error[requestId]; - delete this.listHooks.openAICompat.success[requestId] - delete this.listHooks.openAICompat.error[requestId] + delete this.listHooks.openAICompat.success[requestId]; + delete this.listHooks.openAICompat.error[requestId]; } } diff --git a/src/vs/workbench/contrib/cortexide/electron-main/mcpChannel.ts b/src/vs/workbench/contrib/cortexide/electron-main/mcpChannel.ts index 2dbd6d8d291..945a0e1bdf3 100644 --- a/src/vs/workbench/contrib/cortexide/electron-main/mcpChannel.ts +++ b/src/vs/workbench/contrib/cortexide/electron-main/mcpChannel.ts @@ -1,7 +1,7 @@ -/*-------------------------------------------------------------------------------------- - * Copyright 2025 Glass Devtools, Inc. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. - *--------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ // registered in app.ts // can't make a service responsible for this, because it needs @@ -23,32 +23,32 @@ const getClientConfig = (serverName: string) => { name: `${serverName}-client`, version: '0.1.0', // debug: true, - } -} + }; +}; -type MCPServerNonError = MCPServer & { status: Omit } -type MCPServerError = MCPServer & { status: 'error' } +type MCPServerNonError = MCPServer & { status: Omit }; +type MCPServerError = MCPServer & { status: 'error' }; type ClientInfo = { - _client: Client, // _client is the client that connects with an mcp client. We're calling mcp clients "server" everywhere except here for naming consistency. - mcpServerEntryJSON: MCPConfigFileEntryJSON, - mcpServer: MCPServerNonError, + _client: Client; // _client is the client that connects with an mcp client. We're calling mcp clients "server" everywhere except here for naming consistency. + mcpServerEntryJSON: MCPConfigFileEntryJSON; + mcpServer: MCPServerNonError; } | { - _client?: undefined, - mcpServerEntryJSON: MCPConfigFileEntryJSON, - mcpServer: MCPServerError, -} + _client?: undefined; + mcpServerEntryJSON: MCPConfigFileEntryJSON; + mcpServer: MCPServerError; +}; type InfoOfClientId = { - [clientId: string]: ClientInfo -} + [clientId: string]: ClientInfo; +}; export class MCPChannel implements IServerChannel { - private readonly infoOfClientId: InfoOfClientId = {} - private readonly _refreshingServerNames: Set = new Set() + private readonly infoOfClientId: InfoOfClientId = {}; + private readonly _refreshingServerNames: Set = new Set(); // mcp emitters private readonly mcpEmitters = { @@ -59,60 +59,60 @@ export class MCPChannel implements IServerChannel { } } satisfies { serverEvent: { - onAdd: Emitter, - onUpdate: Emitter, - onDelete: Emitter, - } - } + onAdd: Emitter; + onUpdate: Emitter; + onDelete: Emitter; + }; + }; constructor( ) { } // browser uses this to listen for changes - listen(_: unknown, event: string): Event { + listen(_: unknown, event: string): Event { // server events - if (event === 'onAdd_server') return this.mcpEmitters.serverEvent.onAdd.event; - else if (event === 'onUpdate_server') return this.mcpEmitters.serverEvent.onUpdate.event; - else if (event === 'onDelete_server') return this.mcpEmitters.serverEvent.onDelete.event; + if (event === 'onAdd_server') { return this.mcpEmitters.serverEvent.onAdd.event; } + else if (event === 'onUpdate_server') { return this.mcpEmitters.serverEvent.onUpdate.event; } + else if (event === 'onDelete_server') { return this.mcpEmitters.serverEvent.onDelete.event; } // else if (event === 'onLoading_server') return this.mcpEmitters.serverEvent.onChangeLoading.event; // tool call events // handle unknown events - else throw new Error(`Event not found: ${event}`); + else { throw new Error(`Event not found: ${event}`); } } // browser uses this to call (see this.channel.call() in mcpConfigService.ts for all usages) - async call(_: unknown, command: string, params: any): Promise { + async call(_: unknown, command: string, params: unknown): Promise { try { if (command === 'refreshMCPServers') { - await this._refreshMCPServers(params) + await this._refreshMCPServers(params); } else if (command === 'closeAllMCPServers') { - await this._closeAllMCPServers() + await this._closeAllMCPServers(); } else if (command === 'toggleMCPServer') { - await this._toggleMCPServer(params.serverName, params.isOn) + await this._toggleMCPServer(params.serverName, params.isOn); } else if (command === 'callTool') { - const p: MCPToolCallParams = params - const response = await this._safeCallTool(p.serverName, p.toolName, p.params) - return response + const p: MCPToolCallParams = params; + const response = await this._safeCallTool(p.serverName, p.toolName, p.params); + return response; } else { - throw new Error(`CortexIDE: command "${command}" not recognized.`) + throw new Error(`CortexIDE: command "${command}" not recognized.`); } } catch (e) { - console.error('mcp channel: Call Error:', e) + console.error('mcp channel: Call Error:', e); } } // server functions - private async _refreshMCPServers(params: { mcpConfigFileJSON: MCPConfigFileJSON, userStateOfName: MCPUserStateOfName, addedServerNames: string[], removedServerNames: string[], updatedServerNames: string[] }) { + private async _refreshMCPServers(params: { mcpConfigFileJSON: MCPConfigFileJSON; userStateOfName: MCPUserStateOfName; addedServerNames: string[]; removedServerNames: string[]; updatedServerNames: string[] }) { const { mcpConfigFileJSON, @@ -120,78 +120,108 @@ export class MCPChannel implements IServerChannel { addedServerNames, removedServerNames, updatedServerNames, - } = params + } = params; - const { mcpServers: mcpServersJSON } = mcpConfigFileJSON + const { mcpServers: mcpServersJSON } = mcpConfigFileJSON; - const allChanges: { type: 'added' | 'removed' | 'updated', serverName: string }[] = [ + const allChanges: { type: 'added' | 'removed' | 'updated'; serverName: string }[] = [ ...addedServerNames.map(n => ({ serverName: n, type: 'added' }) as const), ...removedServerNames.map(n => ({ serverName: n, type: 'removed' }) as const), ...updatedServerNames.map(n => ({ serverName: n, type: 'updated' }) as const), - ] + ]; await Promise.all( allChanges.map(async ({ serverName, type }) => { // check if already refreshing - if (this._refreshingServerNames.has(serverName)) return - this._refreshingServerNames.add(serverName) + if (this._refreshingServerNames.has(serverName)) { return; } + this._refreshingServerNames.add(serverName); const prevServer = this.infoOfClientId[serverName]?.mcpServer; // close and delete the old client if (type === 'removed' || type === 'updated') { - await this._closeClient(serverName) - delete this.infoOfClientId[serverName] - this.mcpEmitters.serverEvent.onDelete.fire({ response: { prevServer, name: serverName, } }) + await this._closeClient(serverName); + delete this.infoOfClientId[serverName]; + this.mcpEmitters.serverEvent.onDelete.fire({ response: { prevServer, name: serverName, } }); } // create a new client if (type === 'added' || type === 'updated') { - const clientInfo = await this._createClient(mcpServersJSON[serverName], serverName, userStateOfName[serverName]?.isOn) - this.infoOfClientId[serverName] = clientInfo - this.mcpEmitters.serverEvent.onAdd.fire({ response: { newServer: clientInfo.mcpServer, name: serverName, } }) + const clientInfo = await this._createClient(mcpServersJSON[serverName], serverName, userStateOfName[serverName]?.isOn); + this.infoOfClientId[serverName] = clientInfo; + this.mcpEmitters.serverEvent.onAdd.fire({ response: { newServer: clientInfo.mcpServer, name: serverName, } }); } }) - ) + ); allChanges.forEach(({ serverName, type }) => { - this._refreshingServerNames.delete(serverName) - }) + this._refreshingServerNames.delete(serverName); + }); } private async _createClientUnsafe(server: MCPConfigFileEntryJSON, serverName: string, isOn: boolean): Promise { - const clientConfig = getClientConfig(serverName) - const client = new Client(clientConfig) + const clientConfig = getClientConfig(serverName); + const client = new Client(clientConfig); let transport: Transport; let info: MCPServerNonError; if (server.url) { - // first try HTTP, fall back to SSE - try { - transport = new StreamableHTTPClientTransport(server.url); + // Ensure URL is a URL object (convert from string if needed) + const url = typeof server.url === 'string' ? new URL(server.url) : server.url; + + // Check if type is explicitly specified + if (server.type === 'sse') { + // Use SSE transport directly + transport = new SSEClientTransport(url); await client.connect(transport); - console.log(`Connected via HTTP to ${serverName}`); - const { tools } = await client.listTools() - const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })) + console.log(`Connected via SSE to ${serverName}`); + const { tools } = await client.listTools(); + const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })); info = { status: isOn ? 'success' : 'offline', tools: toolsWithUniqueName, - command: server.url.toString(), - } - } catch (httpErr) { - console.warn(`HTTP failed for ${serverName}, trying SSE…`, httpErr); - transport = new SSEClientTransport(server.url); + command: url.toString(), + }; + } else if (server.type === 'http') { + // Use HTTP transport directly + transport = new StreamableHTTPClientTransport(url); await client.connect(transport); - const { tools } = await client.listTools() - const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })) - console.log(`Connected via SSE to ${serverName}`); + console.log(`Connected via HTTP to ${serverName}`); + const { tools } = await client.listTools(); + const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })); info = { status: isOn ? 'success' : 'offline', tools: toolsWithUniqueName, - command: server.url.toString(), + command: url.toString(), + }; + } else { + // No type specified: first try HTTP, fall back to SSE + try { + transport = new StreamableHTTPClientTransport(url); + await client.connect(transport); + console.log(`Connected via HTTP to ${serverName}`); + const { tools } = await client.listTools(); + const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })); + info = { + status: isOn ? 'success' : 'offline', + tools: toolsWithUniqueName, + command: url.toString(), + }; + } catch (httpErr) { + console.warn(`HTTP failed for ${serverName}, trying SSE…`, httpErr); + transport = new SSEClientTransport(url); + await client.connect(transport); + const { tools } = await client.listTools(); + const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })); + console.log(`Connected via SSE to ${serverName}`); + info = { + status: isOn ? 'success' : 'offline', + tools: toolsWithUniqueName, + command: url.toString(), + }; } } } else if (server.command) { @@ -205,28 +235,28 @@ export class MCPChannel implements IServerChannel { } as Record, }); - await client.connect(transport) + await client.connect(transport); // Get the tools from the server - const { tools } = await client.listTools() - const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })) + const { tools } = await client.listTools(); + const toolsWithUniqueName = tools.map(({ name, ...rest }) => ({ name: this._addUniquePrefix(name), ...rest })); // Create a full command string for display - const fullCommand = `${server.command} ${server.args?.join(' ') || ''}` + const fullCommand = `${server.command} ${server.args?.join(' ') || ''}`; // Format server object info = { status: isOn ? 'success' : 'offline', tools: toolsWithUniqueName, command: fullCommand, - } + }; } else { throw new Error(`No url or command for server ${serverName}`); } - return { _client: client, mcpServerEntryJSON: server, mcpServer: info } + return { _client: client, mcpServerEntryJSON: server, mcpServer: info }; } private _addUniquePrefix(base: string) { @@ -235,54 +265,55 @@ export class MCPChannel implements IServerChannel { private async _createClient(serverConfig: MCPConfigFileEntryJSON, serverName: string, isOn = true): Promise { try { - const c: ClientInfo = await this._createClientUnsafe(serverConfig, serverName, isOn) - return c + const c: ClientInfo = await this._createClientUnsafe(serverConfig, serverName, isOn); + return c; } catch (err) { - console.error(`❌ Failed to connect to server "${serverName}":`, err) - const fullCommand = !serverConfig.command ? '' : `${serverConfig.command} ${serverConfig.args?.join(' ') || ''}` - const c: MCPServerError = { status: 'error', error: err + '', command: fullCommand, } - return { mcpServerEntryJSON: serverConfig, mcpServer: c, } + // allow-any-unicode-next-line + console.error(`❌ Failed to connect to server "${serverName}":`, err); + const fullCommand = !serverConfig.command ? '' : `${serverConfig.command} ${serverConfig.args?.join(' ') || ''}`; + const c: MCPServerError = { status: 'error', error: err + '', command: fullCommand, }; + return { mcpServerEntryJSON: serverConfig, mcpServer: c, }; } } private async _closeAllMCPServers() { for (const serverName in this.infoOfClientId) { - await this._closeClient(serverName) - delete this.infoOfClientId[serverName] + await this._closeClient(serverName); + delete this.infoOfClientId[serverName]; } console.log('Closed all MCP servers'); } private async _closeClient(serverName: string) { - const info = this.infoOfClientId[serverName] - if (!info) return - const { _client: client } = info + const info = this.infoOfClientId[serverName]; + if (!info) { return; } + const { _client: client } = info; if (client) { - await client.close() + await client.close(); } console.log(`Closed MCP server ${serverName}`); } private async _toggleMCPServer(serverName: string, isOn: boolean) { - const prevServer = this.infoOfClientId[serverName]?.mcpServer + const prevServer = this.infoOfClientId[serverName]?.mcpServer; // Handle turning on the server if (isOn) { // this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn)) - const clientInfo = await this._createClientUnsafe(this.infoOfClientId[serverName].mcpServerEntryJSON, serverName, isOn) + const clientInfo = await this._createClientUnsafe(this.infoOfClientId[serverName].mcpServerEntryJSON, serverName, isOn); this.mcpEmitters.serverEvent.onUpdate.fire({ response: { name: serverName, newServer: clientInfo.mcpServer, prevServer: prevServer, } - }) + }); } // Handle turning off the server else { // this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn)) - this._closeClient(serverName) - delete this.infoOfClientId[serverName]._client + this._closeClient(serverName); + delete this.infoOfClientId[serverName]._client; this.mcpEmitters.serverEvent.onUpdate.fire({ response: { @@ -296,31 +327,31 @@ export class MCPChannel implements IServerChannel { }, prevServer: prevServer, } - }) + }); } } // tool call functions - private async _callTool(serverName: string, toolName: string, params: any): Promise { - const server = this.infoOfClientId[serverName] - if (!server) throw new Error(`Server ${serverName} not found`) - const { _client: client } = server - if (!client) throw new Error(`Client for server ${serverName} not found`) + private async _callTool(serverName: string, toolName: string, params: unknown): Promise { + const server = this.infoOfClientId[serverName]; + if (!server) { throw new Error(`Server ${serverName} not found`); } + const { _client: client } = server; + if (!client) { throw new Error(`Client for server ${serverName} not found`); } // Call the tool with the provided parameters const response = await client.callTool({ name: removeMCPToolNamePrefix(toolName), arguments: params - }) - const { content } = response as CallToolResult - const returnValue = content[0] + }); + const { content } = response as CallToolResult; + const returnValue = content[0]; if (returnValue.type === 'text') { // handle text response if (response.isError) { - throw new Error(`Tool call error: ${returnValue.text}`) + throw new Error(`Tool call error: ${returnValue.text}`); } // handle success @@ -329,7 +360,7 @@ export class MCPChannel implements IServerChannel { text: returnValue.text, toolName, serverName, - } + }; } // if (returnValue.type === 'audio') { @@ -344,32 +375,27 @@ export class MCPChannel implements IServerChannel { // // handle resource response // } - throw new Error(`Tool call error: We don\'t support ${returnValue.type} tool response yet for tool ${toolName} on server ${serverName}`) + throw new Error(`Tool call error: We don\'t support ${returnValue.type} tool response yet for tool ${toolName} on server ${serverName}`); } // tool call error wrapper - private async _safeCallTool(serverName: string, toolName: string, params: any): Promise { + private async _safeCallTool(serverName: string, toolName: string, params: unknown): Promise { try { - const response = await this._callTool(serverName, toolName, params) - return response + const response = await this._callTool(serverName, toolName, params); + return response; } catch (err) { let errorMessage: string; if (typeof err === 'object' && err !== null && err['code']) { - const code = err.code - let codeDescription = '' - if (code === -32700) - codeDescription = 'Parse Error'; - if (code === -32600) - codeDescription = 'Invalid Request'; - if (code === -32601) - codeDescription = 'Method Not Found'; - if (code === -32602) - codeDescription = 'Invalid Parameters'; - if (code === -32603) - codeDescription = 'Internal Error'; - errorMessage = `${codeDescription}. Full response:\n${JSON.stringify(err, null, 2)}` + const code = err.code; + let codeDescription = ''; + if (code === -32700) { codeDescription = 'Parse Error'; } + if (code === -32600) { codeDescription = 'Invalid Request'; } + if (code === -32601) { codeDescription = 'Method Not Found'; } + if (code === -32602) { codeDescription = 'Invalid Parameters'; } + if (code === -32603) { codeDescription = 'Internal Error'; } + errorMessage = `${codeDescription}. Full response:\n${JSON.stringify(err, null, 2)}`; } // Check if it's an MCP error with a code else if (typeof err === 'string') { @@ -380,14 +406,15 @@ export class MCPChannel implements IServerChannel { errorMessage = JSON.stringify(err, null, 2); } + // allow-any-unicode-next-line const fullErrorMessage = `❌ Failed to call tool "${toolName}" on server "${serverName}": ${errorMessage}`; const errorResponse: MCPToolErrorResponse = { event: 'error', text: fullErrorMessage, toolName, serverName, - } - return errorResponse + }; + return errorResponse; } } } From ce93ea9948f38debb1c88c41c9a69660ef770423 Mon Sep 17 00:00:00 2001 From: Tajudeen Date: Fri, 21 Nov 2025 23:13:17 +0000 Subject: [PATCH 04/44] WIP: Various improvements and updates --- .../browser/react/out/chunk-6FX43ENS.js | 26 + .../browser/react/out/chunk-GKOTMYUK.js | 11930 +++++++++ .../browser/react/out/chunk-JSBRDJBE.js | 27 + .../browser/react/out/chunk-PT4A2IRQ.js | 415 + .../browser/react/out/chunk-RJP66NWB.js | 22073 ++++++++++++++++ .../browser/react/out/chunk-RM77YOHK.js | 11930 +++++++++ .../browser/react/out/chunk-SWVXQVDT.js | 1479 ++ .../cortexide/browser/react/out/diff/index.js | 555 + .../browser/react/out/quick-edit-tsx/index.js | 142 + .../browser/react/out/sidebar-tsx/index.js | 31 + .../react/out/visionModelHelper-N2YMBODM.js | 104 + .../out/void-editor-widgets-tsx/index.js | 402 + .../react/out/void-onboarding/index.js | 476 + .../react/out/void-settings-tsx/index.js | 10 + .../browser/react/out/void-tooltip/index.js | 644 + .../contrib/cortexide/browser/actionIDs.ts | 4 + .../cortexide/browser/chatThreadService.ts | 151 +- .../browser/contextGatheringService.ts | 11 +- .../browser/convertToLLMMessageService.ts | 48 +- .../browser/cortexideCommandBarService.ts | 42 +- .../browser/cortexideSettingsPane.ts | 4 +- .../cortexide/browser/editCodeService.ts | 4 + .../contrib/cortexide/browser/quickActions.ts | 57 +- .../contrib/cortexide/browser/react/build.js | 278 + .../react/src/sidebar-tsx/ChatTabsBar.tsx | 4 +- .../react/src/sidebar-tsx/SidebarChat.tsx | 215 +- .../browser/react/src/util/inputs.tsx | 235 +- .../react/src/util/mountFnGenerator.tsx | 21 +- .../VoidSelectionHelper.tsx | 3 +- .../src/void-settings-tsx/ModelDropdown.tsx | 2 +- .../cortexide/browser/react/tsup.config.js | 2 +- .../cortexide/browser/repoIndexerService.ts | 286 +- .../cortexide/browser/sidebarActions.ts | 4 +- .../contrib/cortexide/browser/toolsService.ts | 12 +- .../cortexide/browser/treeSitterService.ts | 5 +- .../cortexide/common/cortexideModelService.ts | 24 +- .../cortexide/common/directoryStrService.ts | 105 +- .../contrib/cortexide/common/mcpService.ts | 13 +- .../contrib/cortexide/common/modelRouter.ts | 14 +- .../cortexide/common/sendLLMMessageService.ts | 13 +- .../cortexide/electron-main/mcpChannel.ts | 43 +- .../files/browser/fileActions.contribution.ts | 2 +- .../progress/browser/editorProgressService.ts | 33 + src/vs/workbench/workbench.common.main.ts | 1 + 44 files changed, 51563 insertions(+), 317 deletions(-) create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-6FX43ENS.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-GKOTMYUK.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-JSBRDJBE.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-PT4A2IRQ.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-RJP66NWB.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-RM77YOHK.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-SWVXQVDT.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/diff/index.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/quick-edit-tsx/index.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/sidebar-tsx/index.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/visionModelHelper-N2YMBODM.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/void-editor-widgets-tsx/index.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/void-onboarding/index.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/void-settings-tsx/index.js create mode 100644 src/out/vs/workbench/contrib/cortexide/browser/react/out/void-tooltip/index.js create mode 100644 src/vs/workbench/services/progress/browser/editorProgressService.ts diff --git a/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-6FX43ENS.js b/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-6FX43ENS.js new file mode 100644 index 00000000000..13ac3184064 --- /dev/null +++ b/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-6FX43ENS.js @@ -0,0 +1,26 @@ +// #style-inject:#style-inject +function styleInject(css, { insertAt } = {}) { + if (!css || typeof document === "undefined") return; + const head = document.head || document.getElementsByTagName("head")[0]; + const style = document.createElement("style"); + style.type = "text/css"; + if (insertAt === "top") { + if (head.firstChild) { + head.insertBefore(style, head.firstChild); + } else { + head.appendChild(style); + } + } else { + head.appendChild(style); + } + if (style.styleSheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } +} + +// src2/styles.css +styleInject('.void-scope {\n}\n.void-scope *,\n.void-scope ::before,\n.void-scope ::after {\n --tw-border-spacing-x: 0;\n --tw-border-spacing-y: 0;\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-rotate: 0;\n --tw-skew-x: 0;\n --tw-skew-y: 0;\n --tw-scale-x: 1;\n --tw-scale-y: 1;\n --tw-pan-x: ;\n --tw-pan-y: ;\n --tw-pinch-zoom: ;\n --tw-scroll-snap-strictness: proximity;\n --tw-gradient-from-position: ;\n --tw-gradient-via-position: ;\n --tw-gradient-to-position: ;\n --tw-ordinal: ;\n --tw-slashed-zero: ;\n --tw-numeric-figure: ;\n --tw-numeric-spacing: ;\n --tw-numeric-fraction: ;\n --tw-ring-inset: ;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-color: rgb(59 130 246 / 0.5);\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-ring-shadow: 0 0 #0000;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-colored: 0 0 #0000;\n --tw-blur: ;\n --tw-brightness: ;\n --tw-contrast: ;\n --tw-grayscale: ;\n --tw-hue-rotate: ;\n --tw-invert: ;\n --tw-saturate: ;\n --tw-sepia: ;\n --tw-drop-shadow: ;\n --tw-backdrop-blur: ;\n --tw-backdrop-brightness: ;\n --tw-backdrop-contrast: ;\n --tw-backdrop-grayscale: ;\n --tw-backdrop-hue-rotate: ;\n --tw-backdrop-invert: ;\n --tw-backdrop-opacity: ;\n --tw-backdrop-saturate: ;\n --tw-backdrop-sepia: ;\n --tw-contain-size: ;\n --tw-contain-layout: ;\n --tw-contain-paint: ;\n --tw-contain-style: ;\n}\n.void-scope ::backdrop {\n --tw-border-spacing-x: 0;\n --tw-border-spacing-y: 0;\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-rotate: 0;\n --tw-skew-x: 0;\n --tw-skew-y: 0;\n --tw-scale-x: 1;\n --tw-scale-y: 1;\n --tw-pan-x: ;\n --tw-pan-y: ;\n --tw-pinch-zoom: ;\n --tw-scroll-snap-strictness: proximity;\n --tw-gradient-from-position: ;\n --tw-gradient-via-position: ;\n --tw-gradient-to-position: ;\n --tw-ordinal: ;\n --tw-slashed-zero: ;\n --tw-numeric-figure: ;\n --tw-numeric-spacing: ;\n --tw-numeric-fraction: ;\n --tw-ring-inset: ;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-color: rgb(59 130 246 / 0.5);\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-ring-shadow: 0 0 #0000;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-colored: 0 0 #0000;\n --tw-blur: ;\n --tw-brightness: ;\n --tw-contrast: ;\n --tw-grayscale: ;\n --tw-hue-rotate: ;\n --tw-invert: ;\n --tw-saturate: ;\n --tw-sepia: ;\n --tw-drop-shadow: ;\n --tw-backdrop-blur: ;\n --tw-backdrop-brightness: ;\n --tw-backdrop-contrast: ;\n --tw-backdrop-grayscale: ;\n --tw-backdrop-hue-rotate: ;\n --tw-backdrop-invert: ;\n --tw-backdrop-opacity: ;\n --tw-backdrop-saturate: ;\n --tw-backdrop-sepia: ;\n --tw-contain-size: ;\n --tw-contain-layout: ;\n --tw-contain-paint: ;\n --tw-contain-style: ;\n}\n.void-scope *,\n.void-scope ::before,\n.void-scope ::after {\n box-sizing: border-box;\n border-width: 0;\n border-style: solid;\n border-color: #e5e7eb;\n}\n.void-scope ::before,\n.void-scope ::after {\n --tw-content: "";\n}\n.void-scope html,\n.void-scope :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n -moz-tab-size: 4;\n -o-tab-size: 4;\n tab-size: 4;\n font-family:\n ui-sans-serif,\n system-ui,\n sans-serif,\n "Apple Color Emoji",\n "Segoe UI Emoji",\n "Segoe UI Symbol",\n "Noto Color Emoji";\n font-feature-settings: normal;\n font-variation-settings: normal;\n -webkit-tap-highlight-color: transparent;\n}\n.void-scope body {\n margin: 0;\n line-height: inherit;\n}\n.void-scope hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n}\n.void-scope abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n}\n.void-scope h1,\n.void-scope h2,\n.void-scope h3,\n.void-scope h4,\n.void-scope h5,\n.void-scope h6 {\n font-size: inherit;\n font-weight: inherit;\n}\n.void-scope a {\n color: inherit;\n text-decoration: inherit;\n}\n.void-scope b,\n.void-scope strong {\n font-weight: bolder;\n}\n.void-scope code,\n.void-scope kbd,\n.void-scope samp,\n.void-scope pre {\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n "Liberation Mono",\n "Courier New",\n monospace;\n font-feature-settings: normal;\n font-variation-settings: normal;\n font-size: 1em;\n}\n.void-scope small {\n font-size: 80%;\n}\n.void-scope sub,\n.void-scope sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n.void-scope sub {\n bottom: -0.25em;\n}\n.void-scope sup {\n top: -0.5em;\n}\n.void-scope table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n}\n.void-scope button,\n.void-scope input,\n.void-scope optgroup,\n.void-scope select,\n.void-scope textarea {\n font-family: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n font-size: 100%;\n font-weight: inherit;\n line-height: inherit;\n letter-spacing: inherit;\n color: inherit;\n margin: 0;\n padding: 0;\n}\n.void-scope button,\n.void-scope select {\n text-transform: none;\n}\n.void-scope button,\n.void-scope input:where([type=button]),\n.void-scope input:where([type=reset]),\n.void-scope input:where([type=submit]) {\n -webkit-appearance: button;\n background-color: transparent;\n background-image: none;\n}\n.void-scope :-moz-focusring {\n outline: auto;\n}\n.void-scope :-moz-ui-invalid {\n box-shadow: none;\n}\n.void-scope progress {\n vertical-align: baseline;\n}\n.void-scope ::-webkit-inner-spin-button,\n.void-scope ::-webkit-outer-spin-button {\n height: auto;\n}\n.void-scope [type=search] {\n -webkit-appearance: textfield;\n outline-offset: -2px;\n}\n.void-scope ::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n.void-scope ::-webkit-file-upload-button {\n -webkit-appearance: button;\n font: inherit;\n}\n.void-scope summary {\n display: list-item;\n}\n.void-scope blockquote,\n.void-scope dl,\n.void-scope dd,\n.void-scope h1,\n.void-scope h2,\n.void-scope h3,\n.void-scope h4,\n.void-scope h5,\n.void-scope h6,\n.void-scope hr,\n.void-scope figure,\n.void-scope p,\n.void-scope pre {\n margin: 0;\n}\n.void-scope fieldset {\n margin: 0;\n padding: 0;\n}\n.void-scope legend {\n padding: 0;\n}\n.void-scope ol,\n.void-scope ul,\n.void-scope menu {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.void-scope dialog {\n padding: 0;\n}\n.void-scope textarea {\n resize: vertical;\n}\n.void-scope input::-moz-placeholder,\n.void-scope textarea::-moz-placeholder {\n opacity: 1;\n color: #9ca3af;\n}\n.void-scope input::placeholder,\n.void-scope textarea::placeholder {\n opacity: 1;\n color: #9ca3af;\n}\n.void-scope button,\n.void-scope [role=button] {\n cursor: pointer;\n}\n.void-scope :disabled {\n cursor: default;\n}\n.void-scope img,\n.void-scope svg,\n.void-scope video,\n.void-scope canvas,\n.void-scope audio,\n.void-scope iframe,\n.void-scope embed,\n.void-scope object {\n display: block;\n vertical-align: middle;\n}\n.void-scope img,\n.void-scope video {\n max-width: 100%;\n height: auto;\n}\n.void-scope [hidden]:where(:not([hidden=until-found])) {\n display: none;\n}\n.void-scope .void-prose {\n color: var(--tw-prose-body);\n max-width: 65ch;\n}\n.void-scope .void-prose :where(p):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.25em;\n margin-bottom: 1.25em;\n}\n.void-scope .void-prose :where([class~=lead]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-lead);\n font-size: 1.25em;\n line-height: 1.6;\n margin-top: 1.2em;\n margin-bottom: 1.2em;\n}\n.void-scope .void-prose :where(a):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-links);\n text-decoration: underline;\n font-weight: 500;\n}\n.void-scope .void-prose :where(strong):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-bold);\n font-weight: 600;\n}\n.void-scope .void-prose :where(a strong):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: inherit;\n}\n.void-scope .void-prose :where(blockquote strong):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: inherit;\n}\n.void-scope .void-prose :where(thead th strong):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: inherit;\n}\n.void-scope .void-prose :where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: decimal;\n margin-top: 1.25em;\n margin-bottom: 1.25em;\n padding-inline-start: 1.625em;\n}\n.void-scope .void-prose :where(ol[type=A]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: upper-alpha;\n}\n.void-scope .void-prose :where(ol[type=a]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: lower-alpha;\n}\n.void-scope .void-prose :where(ol[type=A s]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: upper-alpha;\n}\n.void-scope .void-prose :where(ol[type=a s]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: lower-alpha;\n}\n.void-scope .void-prose :where(ol[type=I]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: upper-roman;\n}\n.void-scope .void-prose :where(ol[type=i]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: lower-roman;\n}\n.void-scope .void-prose :where(ol[type=I s]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: upper-roman;\n}\n.void-scope .void-prose :where(ol[type=i s]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: lower-roman;\n}\n.void-scope .void-prose :where(ol[type="1"]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: decimal;\n}\n.void-scope .void-prose :where(ul):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n list-style-type: disc;\n margin-top: 1.25em;\n margin-bottom: 1.25em;\n padding-inline-start: 1.625em;\n}\n.void-scope .void-prose :where(ol > li):not(:where([class~=void-not-prose], [class~=void-not-prose] *))::marker {\n font-weight: 400;\n color: var(--tw-prose-counters);\n}\n.void-scope .void-prose :where(ul > li):not(:where([class~=void-not-prose], [class~=void-not-prose] *))::marker {\n color: var(--tw-prose-bullets);\n}\n.void-scope .void-prose :where(dt):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-headings);\n font-weight: 600;\n margin-top: 1.25em;\n}\n.void-scope .void-prose :where(hr):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n border-color: var(--tw-prose-hr);\n border-top-width: 1px;\n margin-top: 3em;\n margin-bottom: 3em;\n}\n.void-scope .void-prose :where(blockquote):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-weight: 500;\n font-style: italic;\n color: var(--tw-prose-quotes);\n border-inline-start-width: 0.25rem;\n border-inline-start-color: var(--tw-prose-quote-borders);\n quotes: "\\201c""\\201d""\\2018""\\2019";\n margin-top: 1.6em;\n margin-bottom: 1.6em;\n padding-inline-start: 1em;\n}\n.void-scope .void-prose :where(blockquote p:first-of-type):not(:where([class~=void-not-prose], [class~=void-not-prose] *))::before {\n content: open-quote;\n}\n.void-scope .void-prose :where(blockquote p:last-of-type):not(:where([class~=void-not-prose], [class~=void-not-prose] *))::after {\n content: close-quote;\n}\n.void-scope .void-prose :where(h1):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-headings);\n font-weight: 800;\n font-size: 2.25em;\n margin-top: 0;\n margin-bottom: 0.8888889em;\n line-height: 1.1111111;\n}\n.void-scope .void-prose :where(h1 strong):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-weight: 900;\n color: inherit;\n}\n.void-scope .void-prose :where(h2):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-headings);\n font-weight: 700;\n font-size: 1.5em;\n margin-top: 2em;\n margin-bottom: 1em;\n line-height: 1.3333333;\n}\n.void-scope .void-prose :where(h2 strong):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-weight: 800;\n color: inherit;\n}\n.void-scope .void-prose :where(h3):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-headings);\n font-weight: 600;\n font-size: 1.25em;\n margin-top: 1.6em;\n margin-bottom: 0.6em;\n line-height: 1.6;\n}\n.void-scope .void-prose :where(h3 strong):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-weight: 700;\n color: inherit;\n}\n.void-scope .void-prose :where(h4):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-headings);\n font-weight: 600;\n margin-top: 1.5em;\n margin-bottom: 0.5em;\n line-height: 1.5;\n}\n.void-scope .void-prose :where(h4 strong):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-weight: 700;\n color: inherit;\n}\n.void-scope .void-prose :where(img):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 2em;\n margin-bottom: 2em;\n}\n.void-scope .void-prose :where(picture):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n display: block;\n margin-top: 2em;\n margin-bottom: 2em;\n}\n.void-scope .void-prose :where(video):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 2em;\n margin-bottom: 2em;\n}\n.void-scope .void-prose :where(kbd):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-weight: 500;\n font-family: inherit;\n color: var(--tw-prose-kbd);\n box-shadow: 0 0 0 1px var(--tw-prose-kbd-shadows), 0 3px 0 var(--tw-prose-kbd-shadows);\n font-size: 0.875em;\n border-radius: 0.3125rem;\n padding-top: 0.1875em;\n padding-inline-end: 0.375em;\n padding-bottom: 0.1875em;\n padding-inline-start: 0.375em;\n}\n.void-scope .void-prose :where(code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-code);\n font-weight: 600;\n font-size: 0.875em;\n}\n.void-scope .void-prose :where(code):not(:where([class~=void-not-prose], [class~=void-not-prose] *))::before {\n content: "`";\n}\n.void-scope .void-prose :where(code):not(:where([class~=void-not-prose], [class~=void-not-prose] *))::after {\n content: "`";\n}\n.void-scope .void-prose :where(a code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: inherit;\n}\n.void-scope .void-prose :where(h1 code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: inherit;\n}\n.void-scope .void-prose :where(h2 code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: inherit;\n font-size: 0.875em;\n}\n.void-scope .void-prose :where(h3 code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: inherit;\n font-size: 0.9em;\n}\n.void-scope .void-prose :where(h4 code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: inherit;\n}\n.void-scope .void-prose :where(blockquote code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: inherit;\n}\n.void-scope .void-prose :where(thead th code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: inherit;\n}\n.void-scope .void-prose :where(pre):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-pre-code);\n background-color: var(--tw-prose-pre-bg);\n overflow-x: auto;\n font-weight: 400;\n font-size: 0.875em;\n line-height: 1.7142857;\n margin-top: 1.7142857em;\n margin-bottom: 1.7142857em;\n border-radius: 0.375rem;\n padding-top: 0.8571429em;\n padding-inline-end: 1.1428571em;\n padding-bottom: 0.8571429em;\n padding-inline-start: 1.1428571em;\n}\n.void-scope .void-prose :where(pre code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n background-color: transparent;\n border-width: 0;\n border-radius: 0;\n padding: 0;\n font-weight: inherit;\n color: inherit;\n font-size: inherit;\n font-family: inherit;\n line-height: inherit;\n}\n.void-scope .void-prose :where(pre code):not(:where([class~=void-not-prose], [class~=void-not-prose] *))::before {\n content: none;\n}\n.void-scope .void-prose :where(pre code):not(:where([class~=void-not-prose], [class~=void-not-prose] *))::after {\n content: none;\n}\n.void-scope .void-prose :where(table):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n width: 100%;\n table-layout: auto;\n margin-top: 2em;\n margin-bottom: 2em;\n font-size: 0.875em;\n line-height: 1.7142857;\n}\n.void-scope .void-prose :where(thead):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n border-bottom-width: 1px;\n border-bottom-color: var(--tw-prose-th-borders);\n}\n.void-scope .void-prose :where(thead th):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-headings);\n font-weight: 600;\n vertical-align: bottom;\n padding-inline-end: 0.5714286em;\n padding-bottom: 0.5714286em;\n padding-inline-start: 0.5714286em;\n}\n.void-scope .void-prose :where(tbody tr):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n border-bottom-width: 1px;\n border-bottom-color: var(--tw-prose-td-borders);\n}\n.void-scope .void-prose :where(tbody tr:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n border-bottom-width: 0;\n}\n.void-scope .void-prose :where(tbody td):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n vertical-align: baseline;\n}\n.void-scope .void-prose :where(tfoot):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n border-top-width: 1px;\n border-top-color: var(--tw-prose-th-borders);\n}\n.void-scope .void-prose :where(tfoot td):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n vertical-align: top;\n}\n.void-scope .void-prose :where(th, td):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n text-align: start;\n}\n.void-scope .void-prose :where(figure > *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n margin-bottom: 0;\n}\n.void-scope .void-prose :where(figcaption):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n color: var(--tw-prose-captions);\n font-size: 0.875em;\n line-height: 1.4285714;\n margin-top: 0.8571429em;\n}\n.void-scope .void-prose {\n --tw-prose-body: var(--void-fg-1);\n --tw-prose-headings: var(--void-fg-1);\n --tw-prose-lead: var(--void-fg-2);\n --tw-prose-links: var(--void-link-color);\n --tw-prose-bold: var(--void-fg-1);\n --tw-prose-counters: var(--void-fg-3);\n --tw-prose-bullets: var(--void-fg-3);\n --tw-prose-hr: var(--void-border-4);\n --tw-prose-quotes: var(--void-fg-1);\n --tw-prose-quote-borders: var(--void-border-2);\n --tw-prose-captions: var(--void-fg-3);\n --tw-prose-kbd: #111827;\n --tw-prose-kbd-shadows: rgb(17 24 39 / 10%);\n --tw-prose-code: var(--void-fg-0);\n --tw-prose-pre-code: var(--void-fg-0);\n --tw-prose-pre-bg: var(--void-bg-1);\n --tw-prose-th-borders: var(--void-border-4);\n --tw-prose-td-borders: var(--void-border-4);\n --tw-prose-invert-body: #d1d5db;\n --tw-prose-invert-headings: #fff;\n --tw-prose-invert-lead: #9ca3af;\n --tw-prose-invert-links: #fff;\n --tw-prose-invert-bold: #fff;\n --tw-prose-invert-counters: #9ca3af;\n --tw-prose-invert-bullets: #4b5563;\n --tw-prose-invert-hr: #374151;\n --tw-prose-invert-quotes: #f3f4f6;\n --tw-prose-invert-quote-borders: #374151;\n --tw-prose-invert-captions: #9ca3af;\n --tw-prose-invert-kbd: #fff;\n --tw-prose-invert-kbd-shadows: rgb(255 255 255 / 10%);\n --tw-prose-invert-code: #fff;\n --tw-prose-invert-pre-code: #d1d5db;\n --tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);\n --tw-prose-invert-th-borders: #4b5563;\n --tw-prose-invert-td-borders: #374151;\n font-size: 1rem;\n line-height: 1.75;\n}\n.void-scope .void-prose :where(picture > img):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n margin-bottom: 0;\n}\n.void-scope .void-prose :where(li):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.5em;\n margin-bottom: 0.5em;\n}\n.void-scope .void-prose :where(ol > li):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0.375em;\n}\n.void-scope .void-prose :where(ul > li):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0.375em;\n}\n.void-scope .void-prose :where(.void-prose > ul > li p):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.75em;\n margin-bottom: 0.75em;\n}\n.void-scope .void-prose :where(.void-prose > ul > li > p:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.25em;\n}\n.void-scope .void-prose :where(.void-prose > ul > li > p:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-bottom: 1.25em;\n}\n.void-scope .void-prose :where(.void-prose > ol > li > p:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.25em;\n}\n.void-scope .void-prose :where(.void-prose > ol > li > p:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-bottom: 1.25em;\n}\n.void-scope .void-prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.75em;\n margin-bottom: 0.75em;\n}\n.void-scope .void-prose :where(dl):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.25em;\n margin-bottom: 1.25em;\n}\n.void-scope .void-prose :where(dd):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.5em;\n padding-inline-start: 1.625em;\n}\n.void-scope .void-prose :where(hr + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .void-prose :where(h2 + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .void-prose :where(h3 + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .void-prose :where(h4 + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .void-prose :where(thead th:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0;\n}\n.void-scope .void-prose :where(thead th:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-end: 0;\n}\n.void-scope .void-prose :where(tbody td, tfoot td):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-top: 0.5714286em;\n padding-inline-end: 0.5714286em;\n padding-bottom: 0.5714286em;\n padding-inline-start: 0.5714286em;\n}\n.void-scope .void-prose :where(tbody td:first-child, tfoot td:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0;\n}\n.void-scope .void-prose :where(tbody td:last-child, tfoot td:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-end: 0;\n}\n.void-scope .void-prose :where(figure):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 2em;\n margin-bottom: 2em;\n}\n.void-scope .void-prose :where(.void-prose > :first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .void-prose :where(.void-prose > :last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-bottom: 0;\n}\n.void-scope .void-prose-sm {\n font-size: 0.875rem;\n line-height: 1.7142857;\n}\n.void-scope .void-prose-sm :where(p):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n margin-bottom: 1.1428571em;\n}\n.void-scope .void-prose-sm :where([class~=lead]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 1.2857143em;\n line-height: 1.5555556;\n margin-top: 0.8888889em;\n margin-bottom: 0.8888889em;\n}\n.void-scope .void-prose-sm :where(blockquote):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.3333333em;\n margin-bottom: 1.3333333em;\n padding-inline-start: 1.1111111em;\n}\n.void-scope .void-prose-sm :where(h1):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 2.1428571em;\n margin-top: 0;\n margin-bottom: 0.8em;\n line-height: 1.2;\n}\n.void-scope .void-prose-sm :where(h2):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 1.4285714em;\n margin-top: 1.6em;\n margin-bottom: 0.8em;\n line-height: 1.4;\n}\n.void-scope .void-prose-sm :where(h3):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 1.2857143em;\n margin-top: 1.5555556em;\n margin-bottom: 0.4444444em;\n line-height: 1.5555556;\n}\n.void-scope .void-prose-sm :where(h4):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.4285714em;\n margin-bottom: 0.5714286em;\n line-height: 1.4285714;\n}\n.void-scope .void-prose-sm :where(img):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.7142857em;\n margin-bottom: 1.7142857em;\n}\n.void-scope .void-prose-sm :where(picture):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.7142857em;\n margin-bottom: 1.7142857em;\n}\n.void-scope .void-prose-sm :where(picture > img):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n margin-bottom: 0;\n}\n.void-scope .void-prose-sm :where(video):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.7142857em;\n margin-bottom: 1.7142857em;\n}\n.void-scope .void-prose-sm :where(kbd):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8571429em;\n border-radius: 0.3125rem;\n padding-top: 0.1428571em;\n padding-inline-end: 0.3571429em;\n padding-bottom: 0.1428571em;\n padding-inline-start: 0.3571429em;\n}\n.void-scope .void-prose-sm :where(code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8571429em;\n}\n.void-scope .void-prose-sm :where(h2 code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.9em;\n}\n.void-scope .void-prose-sm :where(h3 code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8888889em;\n}\n.void-scope .void-prose-sm :where(pre):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8571429em;\n line-height: 1.6666667;\n margin-top: 1.6666667em;\n margin-bottom: 1.6666667em;\n border-radius: 0.25rem;\n padding-top: 0.6666667em;\n padding-inline-end: 1em;\n padding-bottom: 0.6666667em;\n padding-inline-start: 1em;\n}\n.void-scope .void-prose-sm :where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n margin-bottom: 1.1428571em;\n padding-inline-start: 1.5714286em;\n}\n.void-scope .void-prose-sm :where(ul):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n margin-bottom: 1.1428571em;\n padding-inline-start: 1.5714286em;\n}\n.void-scope .void-prose-sm :where(li):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.2857143em;\n margin-bottom: 0.2857143em;\n}\n.void-scope .void-prose-sm :where(ol > li):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0.4285714em;\n}\n.void-scope .void-prose-sm :where(ul > li):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0.4285714em;\n}\n.void-scope .void-prose-sm :where(.void-prose-sm > ul > li p):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.5714286em;\n margin-bottom: 0.5714286em;\n}\n.void-scope .void-prose-sm :where(.void-prose-sm > ul > li > p:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n}\n.void-scope .void-prose-sm :where(.void-prose-sm > ul > li > p:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-bottom: 1.1428571em;\n}\n.void-scope .void-prose-sm :where(.void-prose-sm > ol > li > p:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n}\n.void-scope .void-prose-sm :where(.void-prose-sm > ol > li > p:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-bottom: 1.1428571em;\n}\n.void-scope .void-prose-sm :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.5714286em;\n margin-bottom: 0.5714286em;\n}\n.void-scope .void-prose-sm :where(dl):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n margin-bottom: 1.1428571em;\n}\n.void-scope .void-prose-sm :where(dt):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n}\n.void-scope .void-prose-sm :where(dd):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.2857143em;\n padding-inline-start: 1.5714286em;\n}\n.void-scope .void-prose-sm :where(hr):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 2.8571429em;\n margin-bottom: 2.8571429em;\n}\n.void-scope .void-prose-sm :where(hr + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .void-prose-sm :where(h2 + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .void-prose-sm :where(h3 + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .void-prose-sm :where(h4 + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .void-prose-sm :where(table):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8571429em;\n line-height: 1.5;\n}\n.void-scope .void-prose-sm :where(thead th):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-end: 1em;\n padding-bottom: 0.6666667em;\n padding-inline-start: 1em;\n}\n.void-scope .void-prose-sm :where(thead th:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0;\n}\n.void-scope .void-prose-sm :where(thead th:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-end: 0;\n}\n.void-scope .void-prose-sm :where(tbody td, tfoot td):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-top: 0.6666667em;\n padding-inline-end: 1em;\n padding-bottom: 0.6666667em;\n padding-inline-start: 1em;\n}\n.void-scope .void-prose-sm :where(tbody td:first-child, tfoot td:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0;\n}\n.void-scope .void-prose-sm :where(tbody td:last-child, tfoot td:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-end: 0;\n}\n.void-scope .void-prose-sm :where(figure):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.7142857em;\n margin-bottom: 1.7142857em;\n}\n.void-scope .void-prose-sm :where(figure > *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n margin-bottom: 0;\n}\n.void-scope .void-prose-sm :where(figcaption):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8571429em;\n line-height: 1.3333333;\n margin-top: 0.6666667em;\n}\n.void-scope .void-prose-sm :where(.void-prose-sm > :first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .void-prose-sm :where(.void-prose-sm > :last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-bottom: 0;\n}\n.void-scope .void-pointer-events-none {\n pointer-events: none;\n}\n.void-scope .void-pointer-events-auto {\n pointer-events: auto;\n}\n.void-scope .void-fixed {\n position: fixed;\n}\n.void-scope .void-absolute {\n position: absolute;\n}\n.void-scope .void-relative {\n position: relative;\n}\n.void-scope .void-sticky {\n position: sticky;\n}\n.void-scope .void-inset-0 {\n inset: 0px;\n}\n.void-scope .-void-left-\\[999999px\\] {\n left: -999999px;\n}\n.void-scope .-void-right-1 {\n right: -0.25rem;\n}\n.void-scope .-void-top-1 {\n top: -0.25rem;\n}\n.void-scope .-void-top-\\[999999px\\] {\n top: -999999px;\n}\n.void-scope .void-bottom-0 {\n bottom: 0px;\n}\n.void-scope .void-bottom-16 {\n bottom: 4rem;\n}\n.void-scope .void-bottom-4 {\n bottom: 1rem;\n}\n.void-scope .void-left-0 {\n left: 0px;\n}\n.void-scope .void-left-1\\/2 {\n left: 50%;\n}\n.void-scope .void-left-4 {\n left: 1rem;\n}\n.void-scope .void-right-0 {\n right: 0px;\n}\n.void-scope .void-right-1 {\n right: 0.25rem;\n}\n.void-scope .void-right-4 {\n right: 1rem;\n}\n.void-scope .void-top-0 {\n top: 0px;\n}\n.void-scope .void-top-1 {\n top: 0.25rem;\n}\n.void-scope .void-top-1\\/2 {\n top: 50%;\n}\n.void-scope .void-top-4 {\n top: 1rem;\n}\n.void-scope .void-z-0 {\n z-index: 0;\n}\n.void-scope .void-z-10 {\n z-index: 10;\n}\n.void-scope .void-z-\\[1000\\] {\n z-index: 1000;\n}\n.void-scope .void-z-\\[100\\] {\n z-index: 100;\n}\n.void-scope .void-z-\\[9999999\\] {\n z-index: 9999999;\n}\n.void-scope .void-z-\\[99999\\] {\n z-index: 99999;\n}\n.void-scope .void-z-\\[9999\\] {\n z-index: 9999;\n}\n.void-scope .void-mx-0\\.5 {\n margin-left: 0.125rem;\n margin-right: 0.125rem;\n}\n.void-scope .void-mx-1 {\n margin-left: 0.25rem;\n margin-right: 0.25rem;\n}\n.void-scope .void-mx-2 {\n margin-left: 0.5rem;\n margin-right: 0.5rem;\n}\n.void-scope .void-mx-3 {\n margin-left: 0.75rem;\n margin-right: 0.75rem;\n}\n.void-scope .void-mx-auto {\n margin-left: auto;\n margin-right: auto;\n}\n.void-scope .void-my-1 {\n margin-top: 0.25rem;\n margin-bottom: 0.25rem;\n}\n.void-scope .void-my-2 {\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n}\n.void-scope .void-my-3 {\n margin-top: 0.75rem;\n margin-bottom: 0.75rem;\n}\n.void-scope .void-my-4 {\n margin-top: 1rem;\n margin-bottom: 1rem;\n}\n.void-scope .void-my-auto {\n margin-top: auto;\n margin-bottom: auto;\n}\n.void-scope .void-mb-1 {\n margin-bottom: 0.25rem;\n}\n.void-scope .void-mb-2 {\n margin-bottom: 0.5rem;\n}\n.void-scope .void-mb-3 {\n margin-bottom: 0.75rem;\n}\n.void-scope .void-mb-32 {\n margin-bottom: 8rem;\n}\n.void-scope .void-mb-4 {\n margin-bottom: 1rem;\n}\n.void-scope .void-mb-8 {\n margin-bottom: 2rem;\n}\n.void-scope .void-mb-auto {\n margin-bottom: auto;\n}\n.void-scope .void-ml-1 {\n margin-left: 0.25rem;\n}\n.void-scope .void-ml-2 {\n margin-left: 0.5rem;\n}\n.void-scope .void-ml-4 {\n margin-left: 1rem;\n}\n.void-scope .void-ml-auto {\n margin-left: auto;\n}\n.void-scope .void-mr-0\\.5 {\n margin-right: 0.125rem;\n}\n.void-scope .void-mr-1 {\n margin-right: 0.25rem;\n}\n.void-scope .void-mr-1\\.5 {\n margin-right: 0.375rem;\n}\n.void-scope .void-mt-0\\.5 {\n margin-top: 0.125rem;\n}\n.void-scope .void-mt-1 {\n margin-top: 0.25rem;\n}\n.void-scope .void-mt-12 {\n margin-top: 3rem;\n}\n.void-scope .void-mt-2 {\n margin-top: 0.5rem;\n}\n.void-scope .void-mt-3 {\n margin-top: 0.75rem;\n}\n.void-scope .void-mt-4 {\n margin-top: 1rem;\n}\n.void-scope .void-mt-6 {\n margin-top: 1.5rem;\n}\n.void-scope .void-mt-8 {\n margin-top: 2rem;\n}\n.void-scope .void-line-clamp-2 {\n overflow: hidden;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n.void-scope .void-line-clamp-3 {\n overflow: hidden;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 3;\n}\n.void-scope .void-block {\n display: block;\n}\n.void-scope .void-inline-block {\n display: inline-block;\n}\n.void-scope .void-flex {\n display: flex;\n}\n.void-scope .void-inline-flex {\n display: inline-flex;\n}\n.void-scope .void-grid {\n display: grid;\n}\n.void-scope .void-hidden {\n display: none;\n}\n.void-scope .void-aspect-\\[3\\/4\\] {\n aspect-ratio: 3/4;\n}\n.void-scope .void-aspect-square {\n aspect-ratio: 1 / 1;\n}\n.void-scope .void-size-1\\.5 {\n width: 0.375rem;\n height: 0.375rem;\n}\n.void-scope .void-size-3 {\n width: 0.75rem;\n height: 0.75rem;\n}\n.void-scope .void-size-3\\.5 {\n width: 0.875rem;\n height: 0.875rem;\n}\n.void-scope .void-size-4 {\n width: 1rem;\n height: 1rem;\n}\n.void-scope .void-size-5 {\n width: 1.25rem;\n height: 1.25rem;\n}\n.void-scope .void-size-\\[11px\\] {\n width: 11px;\n height: 11px;\n}\n.void-scope .void-size-\\[18px\\] {\n width: 18px;\n height: 18px;\n}\n.void-scope .void-h-0\\.5 {\n height: 0.125rem;\n}\n.void-scope .void-h-1 {\n height: 0.25rem;\n}\n.void-scope .void-h-1\\.5 {\n height: 0.375rem;\n}\n.void-scope .void-h-12 {\n height: 3rem;\n}\n.void-scope .void-h-2 {\n height: 0.5rem;\n}\n.void-scope .void-h-2\\.5 {\n height: 0.625rem;\n}\n.void-scope .void-h-3 {\n height: 0.75rem;\n}\n.void-scope .void-h-3\\.5 {\n height: 0.875rem;\n}\n.void-scope .void-h-4 {\n height: 1rem;\n}\n.void-scope .void-h-5 {\n height: 1.25rem;\n}\n.void-scope .void-h-6 {\n height: 1.5rem;\n}\n.void-scope .void-h-\\[120px\\] {\n height: 120px;\n}\n.void-scope .void-h-\\[1px\\] {\n height: 1px;\n}\n.void-scope .void-h-\\[300px\\] {\n height: 300px;\n}\n.void-scope .void-h-\\[3px\\] {\n height: 3px;\n}\n.void-scope .void-h-\\[80vh\\] {\n height: 80vh;\n}\n.void-scope .void-h-fit {\n height: -moz-fit-content;\n height: fit-content;\n}\n.void-scope .void-h-full {\n height: 100%;\n}\n.void-scope .void-max-h-0 {\n max-height: 0px;\n}\n.void-scope .void-max-h-24 {\n max-height: 6rem;\n}\n.void-scope .void-max-h-32 {\n max-height: 8rem;\n}\n.void-scope .void-max-h-48 {\n max-height: 12rem;\n}\n.void-scope .void-max-h-80 {\n max-height: 20rem;\n}\n.void-scope .void-max-h-96 {\n max-height: 24rem;\n}\n.void-scope .void-max-h-\\[240px\\] {\n max-height: 240px;\n}\n.void-scope .void-max-h-\\[300px\\] {\n max-height: 300px;\n}\n.void-scope .void-max-h-\\[320px\\] {\n max-height: 320px;\n}\n.void-scope .void-max-h-\\[400px\\] {\n max-height: 400px;\n}\n.void-scope .void-max-h-\\[500px\\] {\n max-height: 500px;\n}\n.void-scope .void-max-h-\\[80vh\\] {\n max-height: 80vh;\n}\n.void-scope .void-max-h-\\[90vh\\] {\n max-height: 90vh;\n}\n.void-scope .void-max-h-\\[calc\\(100vh-6rem\\)\\] {\n max-height: calc(100vh - 6rem);\n}\n.void-scope .void-max-h-full {\n max-height: 100%;\n}\n.void-scope .void-min-h-\\[120px\\] {\n min-height: 120px;\n}\n.void-scope .void-min-h-\\[200px\\] {\n min-height: 200px;\n}\n.void-scope .void-min-h-\\[24px\\] {\n min-height: 24px;\n}\n.void-scope .void-min-h-\\[60px\\] {\n min-height: 60px;\n}\n.void-scope .void-min-h-\\[70vh\\] {\n min-height: 70vh;\n}\n.void-scope .void-min-h-\\[75vh\\] {\n min-height: 75vh;\n}\n.void-scope .void-min-h-\\[81px\\] {\n min-height: 81px;\n}\n.void-scope .void-w-1 {\n width: 0.25rem;\n}\n.void-scope .void-w-1\\.5 {\n width: 0.375rem;\n}\n.void-scope .void-w-10 {\n width: 2.5rem;\n}\n.void-scope .void-w-11 {\n width: 2.75rem;\n}\n.void-scope .void-w-12 {\n width: 3rem;\n}\n.void-scope .void-w-2 {\n width: 0.5rem;\n}\n.void-scope .void-w-2\\.5 {\n width: 0.625rem;\n}\n.void-scope .void-w-3 {\n width: 0.75rem;\n}\n.void-scope .void-w-3\\.5 {\n width: 0.875rem;\n}\n.void-scope .void-w-4 {\n width: 1rem;\n}\n.void-scope .void-w-48 {\n width: 12rem;\n}\n.void-scope .void-w-5 {\n width: 1.25rem;\n}\n.void-scope .void-w-6 {\n width: 1.5rem;\n}\n.void-scope .void-w-7 {\n width: 1.75rem;\n}\n.void-scope .void-w-9 {\n width: 2.25rem;\n}\n.void-scope .void-w-\\[0\\.5px\\] {\n width: 0.5px;\n}\n.void-scope .void-w-\\[160px\\] {\n width: 160px;\n}\n.void-scope .void-w-\\[200px\\] {\n width: 200px;\n}\n.void-scope .void-w-fit {\n width: -moz-fit-content;\n width: fit-content;\n}\n.void-scope .void-w-full {\n width: 100%;\n}\n.void-scope .void-w-max {\n width: -moz-max-content;\n width: max-content;\n}\n.void-scope .void-min-w-0 {\n min-width: 0px;\n}\n.void-scope .void-min-w-full {\n min-width: 100%;\n}\n.void-scope .void-max-w-2xl {\n max-width: 42rem;\n}\n.void-scope .void-max-w-32 {\n max-width: 8rem;\n}\n.void-scope .void-max-w-3xl {\n max-width: 48rem;\n}\n.void-scope .void-max-w-48 {\n max-width: 12rem;\n}\n.void-scope .void-max-w-\\[1000px\\] {\n max-width: 1000px;\n}\n.void-scope .void-max-w-\\[1200px\\] {\n max-width: 1200px;\n}\n.void-scope .void-max-w-\\[120px\\] {\n max-width: 120px;\n}\n.void-scope .void-max-w-\\[20px\\] {\n max-width: 20px;\n}\n.void-scope .void-max-w-\\[220px\\] {\n max-width: 220px;\n}\n.void-scope .void-max-w-\\[300px\\] {\n max-width: 300px;\n}\n.void-scope .void-max-w-\\[400px\\] {\n max-width: 400px;\n}\n.void-scope .void-max-w-\\[600px\\] {\n max-width: 600px;\n}\n.void-scope .void-max-w-\\[720px\\] {\n max-width: 720px;\n}\n.void-scope .void-max-w-\\[900px\\] {\n max-width: 900px;\n}\n.void-scope .void-max-w-full {\n max-width: 100%;\n}\n.void-scope .void-max-w-md {\n max-width: 28rem;\n}\n.void-scope .void-max-w-none {\n max-width: none;\n}\n.void-scope .void-max-w-sm {\n max-width: 24rem;\n}\n.void-scope .void-max-w-xl {\n max-width: 36rem;\n}\n.void-scope .void-flex-1 {\n flex: 1 1 0%;\n}\n.void-scope .void-flex-shrink-0 {\n flex-shrink: 0;\n}\n.void-scope .void-shrink-0 {\n flex-shrink: 0;\n}\n.void-scope .void-flex-grow {\n flex-grow: 1;\n}\n.void-scope .void-flex-grow-0 {\n flex-grow: 0;\n}\n.void-scope .void-grow {\n flex-grow: 1;\n}\n.void-scope .-void-translate-x-1\\/2 {\n --tw-translate-x: -50%;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .-void-translate-y-0 {\n --tw-translate-y: -0px;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .-void-translate-y-1\\/2 {\n --tw-translate-y: -50%;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-translate-x-0 {\n --tw-translate-x: 0px;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-translate-x-0\\.5 {\n --tw-translate-x: 0.125rem;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-translate-x-1 {\n --tw-translate-x: 0.25rem;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-translate-x-2\\.5 {\n --tw-translate-x: 0.625rem;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-translate-x-3\\.5 {\n --tw-translate-x: 0.875rem;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-translate-x-5 {\n --tw-translate-x: 1.25rem;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-translate-x-6 {\n --tw-translate-x: 1.5rem;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-translate-y-4 {\n --tw-translate-y: 1rem;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-rotate-90 {\n --tw-rotate: 90deg;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-transform {\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n@keyframes void-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.void-scope .void-animate-spin {\n animation: void-spin 1s linear infinite;\n}\n.void-scope .void-cursor-auto {\n cursor: auto;\n}\n.void-scope .void-cursor-default {\n cursor: default;\n}\n.void-scope .void-cursor-grab {\n cursor: grab;\n}\n.void-scope .void-cursor-not-allowed {\n cursor: not-allowed;\n}\n.void-scope .void-cursor-pointer {\n cursor: pointer;\n}\n.void-scope .void-touch-none {\n touch-action: none;\n}\n.void-scope .void-select-none {\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n}\n.void-scope .\\!void-select-text {\n -webkit-user-select: text !important;\n -moz-user-select: text !important;\n user-select: text !important;\n}\n.void-scope .void-select-text {\n -webkit-user-select: text;\n -moz-user-select: text;\n user-select: text;\n}\n.void-scope .void-resize-none {\n resize: none;\n}\n.void-scope .void-list-decimal {\n list-style-type: decimal;\n}\n.void-scope .void-grid-cols-1 {\n grid-template-columns: repeat(1, minmax(0, 1fr));\n}\n.void-scope .void-grid-cols-2 {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n.void-scope .void-grid-cols-3 {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n.void-scope .void-flex-row {\n flex-direction: row;\n}\n.void-scope .void-flex-col {\n flex-direction: column;\n}\n.void-scope .void-flex-wrap {\n flex-wrap: wrap;\n}\n.void-scope .void-flex-nowrap {\n flex-wrap: nowrap;\n}\n.void-scope .void-items-start {\n align-items: flex-start;\n}\n.void-scope .void-items-end {\n align-items: flex-end;\n}\n.void-scope .void-items-center {\n align-items: center;\n}\n.void-scope .void-justify-end {\n justify-content: flex-end;\n}\n.void-scope .void-justify-center {\n justify-content: center;\n}\n.void-scope .void-justify-between {\n justify-content: space-between;\n}\n.void-scope .void-gap-0 {\n gap: 0px;\n}\n.void-scope .void-gap-0\\.5 {\n gap: 0.125rem;\n}\n.void-scope .void-gap-1 {\n gap: 0.25rem;\n}\n.void-scope .void-gap-1\\.5 {\n gap: 0.375rem;\n}\n.void-scope .void-gap-10 {\n gap: 2.5rem;\n}\n.void-scope .void-gap-12 {\n gap: 3rem;\n}\n.void-scope .void-gap-2 {\n gap: 0.5rem;\n}\n.void-scope .void-gap-3 {\n gap: 0.75rem;\n}\n.void-scope .void-gap-4 {\n gap: 1rem;\n}\n.void-scope .void-gap-6 {\n gap: 1.5rem;\n}\n.void-scope .void-gap-8 {\n gap: 2rem;\n}\n.void-scope .void-gap-x-0\\.5 {\n -moz-column-gap: 0.125rem;\n column-gap: 0.125rem;\n}\n.void-scope .void-gap-x-1 {\n -moz-column-gap: 0.25rem;\n column-gap: 0.25rem;\n}\n.void-scope .void-gap-x-2 {\n -moz-column-gap: 0.5rem;\n column-gap: 0.5rem;\n}\n.void-scope .void-gap-y-1 {\n row-gap: 0.25rem;\n}\n.void-scope .void-gap-y-8 {\n row-gap: 2rem;\n}\n.void-scope .void-space-y-1 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-y-reverse: 0;\n margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));\n margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));\n}\n.void-scope .void-space-y-1\\.5 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-y-reverse: 0;\n margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse)));\n margin-bottom: calc(0.375rem * var(--tw-space-y-reverse));\n}\n.void-scope .void-space-y-2 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-y-reverse: 0;\n margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));\n margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));\n}\n.void-scope .void-space-y-3 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-y-reverse: 0;\n margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));\n margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));\n}\n.void-scope .void-space-y-6 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-y-reverse: 0;\n margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));\n margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));\n}\n.void-scope .void-space-y-8 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-y-reverse: 0;\n margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));\n margin-bottom: calc(2rem * var(--tw-space-y-reverse));\n}\n.void-scope .void-space-y-\\[1px\\] > :not([hidden]) ~ :not([hidden]) {\n --tw-space-y-reverse: 0;\n margin-top: calc(1px * calc(1 - var(--tw-space-y-reverse)));\n margin-bottom: calc(1px * var(--tw-space-y-reverse));\n}\n.void-scope .void-self-end {\n align-self: flex-end;\n}\n.void-scope .void-self-stretch {\n align-self: stretch;\n}\n.void-scope .void-overflow-auto {\n overflow: auto;\n}\n.void-scope .void-overflow-hidden {\n overflow: hidden;\n}\n.void-scope .void-overflow-x-auto {\n overflow-x: auto;\n}\n.void-scope .void-overflow-y-auto {\n overflow-y: auto;\n}\n.void-scope .void-overflow-x-hidden {\n overflow-x: hidden;\n}\n.void-scope .void-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.void-scope .void-text-ellipsis {\n text-overflow: ellipsis;\n}\n.void-scope .void-whitespace-nowrap {\n white-space: nowrap;\n}\n.void-scope .void-whitespace-pre {\n white-space: pre;\n}\n.void-scope .void-whitespace-pre-wrap {\n white-space: pre-wrap;\n}\n.void-scope .void-text-nowrap {\n text-wrap: nowrap;\n}\n.void-scope .void-break-words {\n overflow-wrap: break-word;\n}\n.void-scope .void-rounded {\n border-radius: 0.25rem;\n}\n.void-scope .void-rounded-2xl {\n border-radius: 1rem;\n}\n.void-scope .void-rounded-\\[18px\\] {\n border-radius: 18px;\n}\n.void-scope .void-rounded-\\[28px\\] {\n border-radius: 28px;\n}\n.void-scope .void-rounded-\\[32px\\] {\n border-radius: 32px;\n}\n.void-scope .void-rounded-full {\n border-radius: 9999px;\n}\n.void-scope .void-rounded-lg {\n border-radius: 0.5rem;\n}\n.void-scope .void-rounded-md {\n border-radius: 0.375rem;\n}\n.void-scope .void-rounded-none {\n border-radius: 0px;\n}\n.void-scope .void-rounded-sm {\n border-radius: 0.125rem;\n}\n.void-scope .void-rounded-xl {\n border-radius: 0.75rem;\n}\n.void-scope .void-rounded-b-md {\n border-bottom-right-radius: 0.375rem;\n border-bottom-left-radius: 0.375rem;\n}\n.void-scope .void-rounded-t-lg {\n border-top-left-radius: 0.5rem;\n border-top-right-radius: 0.5rem;\n}\n.void-scope .void-border {\n border-width: 1px;\n}\n.void-scope .void-border-2 {\n border-width: 2px;\n}\n.void-scope .void-border-b {\n border-bottom-width: 1px;\n}\n.void-scope .void-border-b-2 {\n border-bottom-width: 2px;\n}\n.void-scope .void-border-l {\n border-left-width: 1px;\n}\n.void-scope .void-border-l-2 {\n border-left-width: 2px;\n}\n.void-scope .void-border-r {\n border-right-width: 1px;\n}\n.void-scope .void-border-t {\n border-top-width: 1px;\n}\n.void-scope .void-border-\\[rgba\\(255\\,255\\,255\\,0\\.08\\)\\] {\n border-color: rgba(255, 255, 255, 0.08);\n}\n.void-scope .void-border-\\[var\\(--vscode-keybindingLabel-border\\)\\] {\n border-color: var(--vscode-keybindingLabel-border);\n}\n.void-scope .void-border-amber-500\\/20 {\n border-color: rgb(245 158 11 / 0.2);\n}\n.void-scope .void-border-amber-500\\/30 {\n border-color: rgb(245 158 11 / 0.3);\n}\n.void-scope .void-border-blue-500 {\n --tw-border-opacity: 1;\n border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));\n}\n.void-scope .void-border-blue-500\\/20 {\n border-color: rgb(59 130 246 / 0.2);\n}\n.void-scope .void-border-blue-500\\/30 {\n border-color: rgb(59 130 246 / 0.3);\n}\n.void-scope .void-border-gray-500 {\n --tw-border-opacity: 1;\n border-color: rgb(107 114 128 / var(--tw-border-opacity, 1));\n}\n.void-scope .void-border-gray-500\\/20 {\n border-color: rgb(107 114 128 / 0.2);\n}\n.void-scope .void-border-gray-500\\/30 {\n border-color: rgb(107 114 128 / 0.3);\n}\n.void-scope .void-border-green-500 {\n --tw-border-opacity: 1;\n border-color: rgb(34 197 94 / var(--tw-border-opacity, 1));\n}\n.void-scope .void-border-green-500\\/20 {\n border-color: rgb(34 197 94 / 0.2);\n}\n.void-scope .void-border-green-500\\/30 {\n border-color: rgb(34 197 94 / 0.3);\n}\n.void-scope .void-border-orange-500 {\n --tw-border-opacity: 1;\n border-color: rgb(249 115 22 / var(--tw-border-opacity, 1));\n}\n.void-scope .void-border-orange-500\\/20 {\n border-color: rgb(249 115 22 / 0.2);\n}\n.void-scope .void-border-orange-500\\/30 {\n border-color: rgb(249 115 22 / 0.3);\n}\n.void-scope .void-border-purple-500\\/20 {\n border-color: rgb(168 85 247 / 0.2);\n}\n.void-scope .void-border-red-200 {\n --tw-border-opacity: 1;\n border-color: rgb(254 202 202 / var(--tw-border-opacity, 1));\n}\n.void-scope .void-border-red-300 {\n --tw-border-opacity: 1;\n border-color: rgb(252 165 165 / var(--tw-border-opacity, 1));\n}\n.void-scope .void-border-red-500 {\n --tw-border-opacity: 1;\n border-color: rgb(239 68 68 / var(--tw-border-opacity, 1));\n}\n.void-scope .void-border-red-500\\/20 {\n border-color: rgb(239 68 68 / 0.2);\n}\n.void-scope .void-border-red-500\\/30 {\n border-color: rgb(239 68 68 / 0.3);\n}\n.void-scope .void-border-red-500\\/80 {\n border-color: rgb(239 68 68 / 0.8);\n}\n.void-scope .void-border-transparent {\n border-color: transparent;\n}\n.void-scope .void-border-void-border-1 {\n border-color: var(--void-border-1);\n}\n.void-scope .void-border-void-border-2 {\n border-color: var(--void-border-2);\n}\n.void-scope .void-border-void-border-3 {\n border-color: var(--void-border-3);\n}\n.void-scope .void-border-void-fg-1 {\n border-color: var(--void-fg-1);\n}\n.void-scope .void-border-void-fg-3 {\n border-color: var(--void-fg-3);\n}\n.void-scope .void-border-void-fg-4 {\n border-color: var(--void-fg-4);\n}\n.void-scope .void-border-void-warning {\n border-color: var(--void-warning);\n}\n.void-scope .void-border-white\\/10 {\n border-color: rgb(255 255 255 / 0.1);\n}\n.void-scope .void-border-white\\/15 {\n border-color: rgb(255 255 255 / 0.15);\n}\n.void-scope .void-border-yellow-500 {\n --tw-border-opacity: 1;\n border-color: rgb(234 179 8 / var(--tw-border-opacity, 1));\n}\n.void-scope .void-border-yellow-500\\/30 {\n border-color: rgb(234 179 8 / 0.3);\n}\n.void-scope .void-border-zinc-300\\/10 {\n border-color: rgb(212 212 216 / 0.1);\n}\n.void-scope .void-bg-\\[\\#030304\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(3 3 4 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-\\[\\#050507\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(5 5 7 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-\\[\\#0e70c0\\] {\n --tw-bg-opacity: 1;\n background-color: rgb(14 112 192 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-\\[\\#0e70c0\\]\\/80 {\n background-color: rgb(14 112 192 / 0.8);\n}\n.void-scope .void-bg-\\[rgba\\(0\\,0\\,0\\,0\\)\\] {\n background-color: rgba(0, 0, 0, 0);\n}\n.void-scope .void-bg-\\[var\\(--vscode-button-background\\)\\] {\n background-color: var(--vscode-button-background);\n}\n.void-scope .void-bg-\\[var\\(--vscode-button-secondaryBackground\\)\\] {\n background-color: var(--vscode-button-secondaryBackground);\n}\n.void-scope .void-bg-\\[var\\(--vscode-keybindingLabel-background\\)\\] {\n background-color: var(--vscode-keybindingLabel-background);\n}\n.void-scope .void-bg-amber-500\\/10 {\n background-color: rgb(245 158 11 / 0.1);\n}\n.void-scope .void-bg-black {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-black\\/10 {\n background-color: rgb(0 0 0 / 0.1);\n}\n.void-scope .void-bg-black\\/20 {\n background-color: rgb(0 0 0 / 0.2);\n}\n.void-scope .void-bg-black\\/50 {\n background-color: rgb(0 0 0 / 0.5);\n}\n.void-scope .void-bg-black\\/60 {\n background-color: rgb(0 0 0 / 0.6);\n}\n.void-scope .void-bg-black\\/90 {\n background-color: rgb(0 0 0 / 0.9);\n}\n.void-scope .void-bg-blue-500 {\n --tw-bg-opacity: 1;\n background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-blue-500\\/10 {\n background-color: rgb(59 130 246 / 0.1);\n}\n.void-scope .void-bg-blue-500\\/20 {\n background-color: rgb(59 130 246 / 0.2);\n}\n.void-scope .void-bg-gray-500\\/10 {\n background-color: rgb(107 114 128 / 0.1);\n}\n.void-scope .void-bg-gray-500\\/20 {\n background-color: rgb(107 114 128 / 0.2);\n}\n.void-scope .void-bg-green-500 {\n --tw-bg-opacity: 1;\n background-color: rgb(34 197 94 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-green-500\\/10 {\n background-color: rgb(34 197 94 / 0.1);\n}\n.void-scope .void-bg-green-500\\/20 {\n background-color: rgb(34 197 94 / 0.2);\n}\n.void-scope .void-bg-orange-50 {\n --tw-bg-opacity: 1;\n background-color: rgb(255 247 237 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-orange-500 {\n --tw-bg-opacity: 1;\n background-color: rgb(249 115 22 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-orange-500\\/10 {\n background-color: rgb(249 115 22 / 0.1);\n}\n.void-scope .void-bg-orange-500\\/20 {\n background-color: rgb(249 115 22 / 0.2);\n}\n.void-scope .void-bg-purple-500\\/10 {\n background-color: rgb(168 85 247 / 0.1);\n}\n.void-scope .void-bg-red-50 {\n --tw-bg-opacity: 1;\n background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-red-500 {\n --tw-bg-opacity: 1;\n background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-red-500\\/10 {\n background-color: rgb(239 68 68 / 0.1);\n}\n.void-scope .void-bg-red-500\\/20 {\n background-color: rgb(239 68 68 / 0.2);\n}\n.void-scope .void-bg-red-500\\/5 {\n background-color: rgb(239 68 68 / 0.05);\n}\n.void-scope .void-bg-red-600 {\n --tw-bg-opacity: 1;\n background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-red-600\\/80 {\n background-color: rgb(220 38 38 / 0.8);\n}\n.void-scope .void-bg-transparent {\n background-color: transparent;\n}\n.void-scope .void-bg-void-bg-1 {\n background-color: var(--void-bg-1);\n}\n.void-scope .void-bg-void-bg-2 {\n background-color: var(--void-bg-2);\n}\n.void-scope .void-bg-void-bg-2-alt {\n background-color: var(--void-bg-2-alt);\n}\n.void-scope .void-bg-void-bg-3 {\n background-color: var(--void-bg-3);\n}\n.void-scope .void-bg-void-border-1 {\n background-color: var(--void-border-1);\n}\n.void-scope .void-bg-void-border-3 {\n background-color: var(--void-border-3);\n}\n.void-scope .void-bg-void-fg-1 {\n background-color: var(--void-fg-1);\n}\n.void-scope .void-bg-void-fg-3 {\n background-color: var(--void-fg-3);\n}\n.void-scope .void-bg-vscode-disabled-fg {\n background-color: var(--vscode-disabledForeground);\n}\n.void-scope .void-bg-white {\n --tw-bg-opacity: 1;\n background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-white\\/40 {\n background-color: rgb(255 255 255 / 0.4);\n}\n.void-scope .void-bg-white\\/5 {\n background-color: rgb(255 255 255 / 0.05);\n}\n.void-scope .void-bg-yellow-500 {\n --tw-bg-opacity: 1;\n background-color: rgb(234 179 8 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-yellow-500\\/20 {\n background-color: rgb(234 179 8 / 0.2);\n}\n.void-scope .void-bg-zinc-100 {\n --tw-bg-opacity: 1;\n background-color: rgb(244 244 245 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-zinc-700\\/5 {\n background-color: rgb(63 63 70 / 0.05);\n}\n.void-scope .void-bg-zinc-900 {\n --tw-bg-opacity: 1;\n background-color: rgb(24 24 27 / var(--tw-bg-opacity, 1));\n}\n.void-scope .void-bg-opacity-70 {\n --tw-bg-opacity: 0.7;\n}\n.void-scope .void-bg-gradient-to-br {\n background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));\n}\n.void-scope .void-bg-gradient-to-r {\n background-image: linear-gradient(to right, var(--tw-gradient-stops));\n}\n.void-scope .void-bg-none {\n background-image: none;\n}\n.void-scope .void-from-\\[\\#0e70c0\\] {\n --tw-gradient-from: #0e70c0 var(--tw-gradient-from-position);\n --tw-gradient-to: rgb(14 112 192 / 0) var(--tw-gradient-to-position);\n --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);\n}\n.void-scope .void-from-\\[\\#2a2c34\\] {\n --tw-gradient-from: #2a2c34 var(--tw-gradient-from-position);\n --tw-gradient-to: rgb(42 44 52 / 0) var(--tw-gradient-to-position);\n --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);\n}\n.void-scope .void-from-\\[\\#3a3d47\\] {\n --tw-gradient-from: #3a3d47 var(--tw-gradient-from-position);\n --tw-gradient-to: rgb(58 61 71 / 0) var(--tw-gradient-to-position);\n --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);\n}\n.void-scope .void-from-\\[var\\(--cortex-surface-2\\)\\] {\n --tw-gradient-from: var(--cortex-surface-2) var(--tw-gradient-from-position);\n --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);\n --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);\n}\n.void-scope .void-from-white\\/10 {\n --tw-gradient-from: rgb(255 255 255 / 0.1) var(--tw-gradient-from-position);\n --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);\n --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);\n}\n.void-scope .void-via-\\[\\#1b1c23\\] {\n --tw-gradient-to: rgb(27 28 35 / 0) var(--tw-gradient-to-position);\n --tw-gradient-stops:\n var(--tw-gradient-from),\n #1b1c23 var(--tw-gradient-via-position),\n var(--tw-gradient-to);\n}\n.void-scope .void-via-\\[\\#23252c\\] {\n --tw-gradient-to: rgb(35 37 44 / 0) var(--tw-gradient-to-position);\n --tw-gradient-stops:\n var(--tw-gradient-from),\n #23252c var(--tw-gradient-via-position),\n var(--tw-gradient-to);\n}\n.void-scope .void-via-\\[var\\(--cortex-surface-3\\)\\] {\n --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);\n --tw-gradient-stops:\n var(--tw-gradient-from),\n var(--cortex-surface-3) var(--tw-gradient-via-position),\n var(--tw-gradient-to);\n}\n.void-scope .void-via-transparent {\n --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);\n --tw-gradient-stops:\n var(--tw-gradient-from),\n transparent var(--tw-gradient-via-position),\n var(--tw-gradient-to);\n}\n.void-scope .void-to-\\[\\#101117\\] {\n --tw-gradient-to: #101117 var(--tw-gradient-to-position);\n}\n.void-scope .void-to-\\[\\#111216\\] {\n --tw-gradient-to: #111216 var(--tw-gradient-to-position);\n}\n.void-scope .void-to-\\[\\#6b5bff\\] {\n --tw-gradient-to: #6b5bff var(--tw-gradient-to-position);\n}\n.void-scope .void-to-\\[var\\(--cortex-surface-4\\)\\] {\n --tw-gradient-to: var(--cortex-surface-4) var(--tw-gradient-to-position);\n}\n.void-scope .void-to-transparent {\n --tw-gradient-to: transparent var(--tw-gradient-to-position);\n}\n.void-scope .void-fill-current {\n fill: currentColor;\n}\n.void-scope .void-stroke-green-500 {\n stroke: #22c55e;\n}\n.void-scope .void-stroke-red-500 {\n stroke: #ef4444;\n}\n.void-scope .void-stroke-\\[2\\] {\n stroke-width: 2;\n}\n.void-scope .void-stroke-\\[3\\] {\n stroke-width: 3;\n}\n.void-scope .void-object-contain {\n -o-object-fit: contain;\n object-fit: contain;\n}\n.void-scope .void-object-cover {\n -o-object-fit: cover;\n object-fit: cover;\n}\n.void-scope .void-p-0\\.5 {\n padding: 0.125rem;\n}\n.void-scope .void-p-1 {\n padding: 0.25rem;\n}\n.void-scope .void-p-1\\.5 {\n padding: 0.375rem;\n}\n.void-scope .void-p-2 {\n padding: 0.5rem;\n}\n.void-scope .void-p-2\\.5 {\n padding: 0.625rem;\n}\n.void-scope .void-p-3 {\n padding: 0.75rem;\n}\n.void-scope .void-p-4 {\n padding: 1rem;\n}\n.void-scope .void-p-5 {\n padding: 1.25rem;\n}\n.void-scope .void-p-6 {\n padding: 1.5rem;\n}\n.void-scope .void-p-\\[2px\\] {\n padding: 2px;\n}\n.void-scope .void-p-\\[7px\\] {\n padding: 7px;\n}\n.void-scope .\\!void-px-0 {\n padding-left: 0px !important;\n padding-right: 0px !important;\n}\n.void-scope .\\!void-py-0 {\n padding-top: 0px !important;\n padding-bottom: 0px !important;\n}\n.void-scope .void-px-0 {\n padding-left: 0px;\n padding-right: 0px;\n}\n.void-scope .void-px-0\\.5 {\n padding-left: 0.125rem;\n padding-right: 0.125rem;\n}\n.void-scope .void-px-1 {\n padding-left: 0.25rem;\n padding-right: 0.25rem;\n}\n.void-scope .void-px-1\\.5 {\n padding-left: 0.375rem;\n padding-right: 0.375rem;\n}\n.void-scope .void-px-10 {\n padding-left: 2.5rem;\n padding-right: 2.5rem;\n}\n.void-scope .void-px-2 {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n}\n.void-scope .void-px-3 {\n padding-left: 0.75rem;\n padding-right: 0.75rem;\n}\n.void-scope .void-px-4 {\n padding-left: 1rem;\n padding-right: 1rem;\n}\n.void-scope .void-px-5 {\n padding-left: 1.25rem;\n padding-right: 1.25rem;\n}\n.void-scope .void-px-6 {\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n}\n.void-scope .void-px-8 {\n padding-left: 2rem;\n padding-right: 2rem;\n}\n.void-scope .void-py-0\\.5 {\n padding-top: 0.125rem;\n padding-bottom: 0.125rem;\n}\n.void-scope .void-py-1 {\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n}\n.void-scope .void-py-1\\.5 {\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n}\n.void-scope .void-py-10 {\n padding-top: 2.5rem;\n padding-bottom: 2.5rem;\n}\n.void-scope .void-py-12 {\n padding-top: 3rem;\n padding-bottom: 3rem;\n}\n.void-scope .void-py-2 {\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n.void-scope .void-py-2\\.5 {\n padding-top: 0.625rem;\n padding-bottom: 0.625rem;\n}\n.void-scope .void-py-3 {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n}\n.void-scope .void-py-6 {\n padding-top: 1.5rem;\n padding-bottom: 1.5rem;\n}\n.void-scope .void-pb-0\\.5 {\n padding-bottom: 0.125rem;\n}\n.void-scope .void-pb-2 {\n padding-bottom: 0.5rem;\n}\n.void-scope .void-pl-0 {\n padding-left: 0px;\n}\n.void-scope .void-pl-2 {\n padding-left: 0.5rem;\n}\n.void-scope .void-pl-4 {\n padding-left: 1rem;\n}\n.void-scope .void-pl-6 {\n padding-left: 1.5rem;\n}\n.void-scope .void-pr-1 {\n padding-right: 0.25rem;\n}\n.void-scope .void-pr-4 {\n padding-right: 1rem;\n}\n.void-scope .void-pt-1 {\n padding-top: 0.25rem;\n}\n.void-scope .void-pt-2 {\n padding-top: 0.5rem;\n}\n.void-scope .void-pt-3 {\n padding-top: 0.75rem;\n}\n.void-scope .void-pt-6 {\n padding-top: 1.5rem;\n}\n.void-scope .void-pt-8 {\n padding-top: 2rem;\n}\n.void-scope .void-text-left {\n text-align: left;\n}\n.void-scope .void-text-center {\n text-align: center;\n}\n.void-scope .void-text-right {\n text-align: right;\n}\n.void-scope .void-align-text-top {\n vertical-align: text-top;\n}\n.void-scope .void-font-mono {\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n "Liberation Mono",\n "Courier New",\n monospace;\n}\n.void-scope .void-text-2xl {\n font-size: 18px;\n}\n.void-scope .void-text-3xl {\n font-size: 20px;\n}\n.void-scope .void-text-4xl {\n font-size: 24px;\n}\n.void-scope .void-text-5xl {\n font-size: 30px;\n}\n.void-scope .void-text-\\[10px\\] {\n font-size: 10px;\n}\n.void-scope .void-text-\\[11px\\] {\n font-size: 11px;\n}\n.void-scope .void-text-\\[13px\\] {\n font-size: 13px;\n}\n.void-scope .void-text-\\[8px\\] {\n font-size: 8px;\n}\n.void-scope .void-text-base {\n font-size: 1rem;\n line-height: 1.5rem;\n}\n.void-scope .void-text-lg {\n font-size: 14px;\n}\n.void-scope .void-text-root {\n font-size: 13px;\n}\n.void-scope .void-text-sm {\n font-size: 11px;\n}\n.void-scope .void-text-xl {\n font-size: 16px;\n}\n.void-scope .void-text-xs {\n font-size: 10px;\n}\n.void-scope .void-font-light {\n font-weight: 300;\n}\n.void-scope .void-font-medium {\n font-weight: 500;\n}\n.void-scope .void-font-semibold {\n font-weight: 600;\n}\n.void-scope .void-uppercase {\n text-transform: uppercase;\n}\n.void-scope .void-italic {\n font-style: italic;\n}\n.void-scope .void-not-italic {\n font-style: normal;\n}\n.void-scope .void-leading-relaxed {\n line-height: 1.625;\n}\n.void-scope .void-leading-snug {\n line-height: 1.375;\n}\n.void-scope .void-tracking-\\[0\\.35em\\] {\n letter-spacing: 0.35em;\n}\n.void-scope .void-tracking-\\[0\\.3em\\] {\n letter-spacing: 0.3em;\n}\n.void-scope .void-tracking-\\[0\\.45em\\] {\n letter-spacing: 0.45em;\n}\n.void-scope .void-tracking-\\[0\\.4em\\] {\n letter-spacing: 0.4em;\n}\n.void-scope .void-tracking-tight {\n letter-spacing: -0.025em;\n}\n.void-scope .void-tracking-wide {\n letter-spacing: 0.025em;\n}\n.void-scope .\\!void-text-void-fg-3 {\n color: var(--void-fg-3) !important;\n}\n.void-scope .void-text-\\[\\#0e70c0\\] {\n --tw-text-opacity: 1;\n color: rgb(14 112 192 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-\\[var\\(--vscode-button-foreground\\)\\] {\n color: var(--vscode-button-foreground);\n}\n.void-scope .void-text-\\[var\\(--vscode-button-secondaryForeground\\)\\] {\n color: var(--vscode-button-secondaryForeground);\n}\n.void-scope .void-text-\\[var\\(--vscode-keybindingLabel-foreground\\)\\] {\n color: var(--vscode-keybindingLabel-foreground);\n}\n.void-scope .void-text-amber-300 {\n --tw-text-opacity: 1;\n color: rgb(252 211 77 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-amber-400 {\n --tw-text-opacity: 1;\n color: rgb(251 191 36 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-blue-300 {\n --tw-text-opacity: 1;\n color: rgb(147 197 253 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-blue-400 {\n --tw-text-opacity: 1;\n color: rgb(96 165 250 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-blue-500 {\n --tw-text-opacity: 1;\n color: rgb(59 130 246 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-emerald-400 {\n --tw-text-opacity: 1;\n color: rgb(52 211 153 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-emerald-500 {\n --tw-text-opacity: 1;\n color: rgb(16 185 129 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-gray-400 {\n --tw-text-opacity: 1;\n color: rgb(156 163 175 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-green-300 {\n --tw-text-opacity: 1;\n color: rgb(134 239 172 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-green-400 {\n --tw-text-opacity: 1;\n color: rgb(74 222 128 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-green-500 {\n --tw-text-opacity: 1;\n color: rgb(34 197 94 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-orange-400 {\n --tw-text-opacity: 1;\n color: rgb(251 146 60 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-orange-500 {\n --tw-text-opacity: 1;\n color: rgb(249 115 22 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-purple-400 {\n --tw-text-opacity: 1;\n color: rgb(192 132 252 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-red-300 {\n --tw-text-opacity: 1;\n color: rgb(252 165 165 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-red-400 {\n --tw-text-opacity: 1;\n color: rgb(248 113 113 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-red-500 {\n --tw-text-opacity: 1;\n color: rgb(239 68 68 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-red-600 {\n --tw-text-opacity: 1;\n color: rgb(220 38 38 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-red-700 {\n --tw-text-opacity: 1;\n color: rgb(185 28 28 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-red-800 {\n --tw-text-opacity: 1;\n color: rgb(153 27 27 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-rose-600 {\n --tw-text-opacity: 1;\n color: rgb(225 29 72 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-void-fg-0 {\n color: var(--void-fg-0);\n}\n.void-scope .void-text-void-fg-1 {\n color: var(--void-fg-1);\n}\n.void-scope .void-text-void-fg-2 {\n color: var(--void-fg-2);\n}\n.void-scope .void-text-void-fg-3 {\n color: var(--void-fg-3);\n}\n.void-scope .void-text-void-fg-4 {\n color: var(--void-fg-4);\n}\n.void-scope .void-text-void-warning {\n color: var(--void-warning);\n}\n.void-scope .void-text-white {\n --tw-text-opacity: 1;\n color: rgb(255 255 255 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-white\\/35 {\n color: rgb(255 255 255 / 0.35);\n}\n.void-scope .void-text-white\\/40 {\n color: rgb(255 255 255 / 0.4);\n}\n.void-scope .void-text-white\\/70 {\n color: rgb(255 255 255 / 0.7);\n}\n.void-scope .void-text-white\\/80 {\n color: rgb(255 255 255 / 0.8);\n}\n.void-scope .void-text-yellow-400 {\n --tw-text-opacity: 1;\n color: rgb(250 204 21 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-text-zinc-900 {\n --tw-text-opacity: 1;\n color: rgb(24 24 27 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-underline {\n text-decoration-line: underline;\n}\n.void-scope .void-line-through {\n text-decoration-line: line-through;\n}\n.void-scope .void-opacity-0 {\n opacity: 0;\n}\n.void-scope .void-opacity-100 {\n opacity: 1;\n}\n.void-scope .void-opacity-25 {\n opacity: 0.25;\n}\n.void-scope .void-opacity-40 {\n opacity: 0.4;\n}\n.void-scope .void-opacity-50 {\n opacity: 0.5;\n}\n.void-scope .void-opacity-60 {\n opacity: 0.6;\n}\n.void-scope .void-opacity-70 {\n opacity: 0.7;\n}\n.void-scope .void-opacity-75 {\n opacity: 0.75;\n}\n.void-scope .void-opacity-80 {\n opacity: 0.8;\n}\n.void-scope .void-opacity-90 {\n opacity: 0.9;\n}\n.void-scope .void-opacity-95 {\n opacity: 0.95;\n}\n.void-scope .void-shadow {\n --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_0_4px_0px_rgba\\(22\\,163\\,74\\,0\\.6\\)\\] {\n --tw-shadow: 0 0 4px 0px rgba(22,163,74,0.6);\n --tw-shadow-colored: 0 0 4px 0px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_0_4px_0px_rgba\\(234\\,88\\,12\\,0\\.6\\)\\] {\n --tw-shadow: 0 0 4px 0px rgba(234,88,12,0.6);\n --tw-shadow-colored: 0 0 4px 0px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_10px_30px_rgba\\(0\\,0\\,0\\,0\\.35\\)\\] {\n --tw-shadow: 0 10px 30px rgba(0,0,0,0.35);\n --tw-shadow-colored: 0 10px 30px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_18px_40px_rgba\\(28\\,107\\,219\\,0\\.35\\)\\] {\n --tw-shadow: 0 18px 40px rgba(28,107,219,0.35);\n --tw-shadow-colored: 0 18px 40px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_25px_55px_rgba\\(0\\,0\\,0\\,0\\.55\\)\\] {\n --tw-shadow: 0 25px 55px rgba(0,0,0,0.55);\n --tw-shadow-colored: 0 25px 55px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_30px_90px_rgba\\(0\\,0\\,0\\,0\\.45\\)\\] {\n --tw-shadow: 0 30px 90px rgba(0,0,0,0.45);\n --tw-shadow-colored: 0 30px 90px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_35px_80px_rgba\\(0\\,0\\,0\\,0\\.6\\)\\] {\n --tw-shadow: 0 35px 80px rgba(0,0,0,0.6);\n --tw-shadow-colored: 0 35px 80px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_35px_90px_rgba\\(0\\,0\\,0\\,0\\.35\\)\\] {\n --tw-shadow: 0 35px 90px rgba(0,0,0,0.35);\n --tw-shadow-colored: 0 35px 90px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_3px_12px_rgba\\(0\\,0\\,0\\,0\\.45\\)\\] {\n --tw-shadow: 0 3px 12px rgba(0,0,0,0.45);\n --tw-shadow-colored: 0 3px 12px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_45px_110px_rgba\\(0\\,0\\,0\\,0\\.7\\)\\] {\n --tw-shadow: 0 45px 110px rgba(0,0,0,0.7);\n --tw-shadow-colored: 0 45px 110px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_45px_120px_rgba\\(0\\,0\\,0\\,0\\.45\\)\\] {\n --tw-shadow: 0 45px 120px rgba(0,0,0,0.45);\n --tw-shadow-colored: 0 45px 120px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_45px_120px_rgba\\(0\\,0\\,0\\,0\\.95\\)\\] {\n --tw-shadow: 0 45px 120px rgba(0,0,0,0.95);\n --tw-shadow-colored: 0 45px 120px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_60px_140px_rgba\\(0\\,0\\,0\\,0\\.75\\)\\] {\n --tw-shadow: 0 60px 140px rgba(0,0,0,0.75);\n --tw-shadow-colored: 0 60px 140px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-\\[0_8px_20px_rgba\\(0\\,0\\,0\\,0\\.35\\)\\] {\n --tw-shadow: 0 8px 20px rgba(0,0,0,0.35);\n --tw-shadow-colored: 0 8px 20px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-lg {\n --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\n --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-md {\n --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-sm {\n --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-shadow-xl {\n --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);\n --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .void-ring-2 {\n --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n box-shadow:\n var(--tw-ring-offset-shadow),\n var(--tw-ring-shadow),\n var(--tw-shadow, 0 0 #0000);\n}\n.void-scope .void-ring-blue-500 {\n --tw-ring-opacity: 1;\n --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));\n}\n.void-scope .void-blur-3xl {\n --tw-blur: blur(64px);\n filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\n}\n.void-scope .void-brightness-90 {\n --tw-brightness: brightness(.9);\n filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\n}\n.void-scope .void-backdrop-blur-2xl {\n --tw-backdrop-blur: blur(40px);\n -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);\n backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);\n}\n.void-scope .void-backdrop-blur-\\[28px\\] {\n --tw-backdrop-blur: blur(28px);\n -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);\n backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);\n}\n.void-scope .void-backdrop-blur-xl {\n --tw-backdrop-blur: blur(24px);\n -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);\n backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);\n}\n.void-scope .void-transition-all {\n transition-property: all;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n}\n.void-scope .void-transition-colors {\n transition-property:\n color,\n background-color,\n border-color,\n text-decoration-color,\n fill,\n stroke;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n}\n.void-scope .void-transition-opacity {\n transition-property: opacity;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n}\n.void-scope .void-transition-transform {\n transition-property: transform;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n}\n.void-scope .void-duration-100 {\n transition-duration: 100ms;\n}\n.void-scope .void-duration-150 {\n transition-duration: 150ms;\n}\n.void-scope .void-duration-200 {\n transition-duration: 200ms;\n}\n.void-scope .void-duration-300 {\n transition-duration: 300ms;\n}\n.void-scope .void-duration-700 {\n transition-duration: 700ms;\n}\n.void-scope .void-ease-\\[cubic-bezier\\(0\\.4\\,0\\,0\\.2\\,1\\)\\] {\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n}\n.void-scope .void-ease-in-out {\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n}\n.void-scope .void-ease-out {\n transition-timing-function: cubic-bezier(0, 0, 0.2, 1);\n}\n.void-scope .select-child-restyle select {\n text-overflow: ellipsis;\n white-space: nowrap;\n padding-right: 24px;\n}\n.void-scope .void-force-child-placeholder-void-fg-1 ::-moz-placeholder {\n color: var(--void-fg-3);\n}\n.void-scope .void-force-child-placeholder-void-fg-1 ::placeholder {\n color: var(--void-fg-3);\n}\n.void-scope * {\n outline: none !important;\n}\n.void-scope .inherit-bg-all-restyle > * {\n background-color: inherit !important;\n}\n.void-scope .bg-editor-style-override {\n --vscode-sideBar-background: var(--vscode-editor-background);\n}\n.void-scope .void-diff-block-header {\n position: sticky;\n top: 0;\n background: var(--void-bg-3);\n border-bottom: 1px solid var(--void-border-4);\n}\n.void-scope .void-focus-ring:focus-visible {\n outline: 1px solid var(--void-ring-color);\n outline-offset: 1px;\n}\n.void-scope .void-density-tight {\n padding: var(--void-space-2) var(--void-space-2);\n}\n.void-scope .void-density-comfortable {\n padding: var(--void-space-4) var(--void-space-4);\n}\n.void-scope .void-density-spacious {\n padding: var(--void-space-6) var(--void-space-6);\n}\n.void-scope .void-transition {\n transition: all var(--void-transition-base);\n}\n.void-scope .void-transition-fast {\n transition: all var(--void-transition-fast);\n}\n.void-scope .void-transition-slow {\n transition: all var(--void-transition-slow);\n}\n.void-scope .void-hover-lift {\n transition: transform var(--void-transition-base), box-shadow var(--void-transition-base);\n}\n.void-scope .void-hover-lift:hover {\n transform: translateY(-1px);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n}\n.void-scope .void-rounded-sm {\n border-radius: var(--void-radius-sm);\n}\n.void-scope .void-rounded-md {\n border-radius: var(--void-radius-md);\n}\n.void-scope .void-rounded-lg {\n border-radius: var(--void-radius-lg);\n}\n.void-scope .void-rounded-xl {\n border-radius: var(--void-radius-xl);\n}\n.void-scope .monaco-editor .code-review-error {\n background-color: rgba(255, 0, 0, 0.1);\n border-left: 3px solid var(--vscode-errorForeground);\n}\n.void-scope .monaco-editor .code-review-warning {\n background-color: rgba(255, 193, 7, 0.1);\n border-left: 3px solid var(--vscode-editorWarning-foreground);\n}\n.void-scope .monaco-editor .code-review-info {\n background-color: rgba(0, 122, 204, 0.1);\n border-left: 3px solid var(--vscode-editorInfo-foreground);\n}\n.void-scope .monaco-editor .code-review-hint {\n background-color: rgba(128, 128, 128, 0.1);\n border-left: 3px solid var(--vscode-editorHint-foreground);\n}\n.void-scope .monaco-editor .code-review-glyph-error::before {\n content: "\\25cf";\n color: var(--vscode-errorForeground);\n font-size: 8px;\n}\n.void-scope .monaco-editor .code-review-glyph-warning::before {\n content: "\\25cf";\n color: var(--vscode-editorWarning-foreground);\n font-size: 8px;\n}\n.void-scope .monaco-editor .code-review-glyph-info::before {\n content: "\\25cf";\n color: var(--vscode-editorInfo-foreground);\n font-size: 8px;\n}\n.void-scope .monaco-editor .code-review-glyph-hint::before {\n content: "\\25cf";\n color: var(--vscode-editorHint-foreground);\n font-size: 8px;\n}\n.void-scope .monaco-editor .error-detection-error {\n background-color: color-mix(in srgb, var(--vscode-errorForeground) 10%, transparent);\n border-left: 3px solid var(--vscode-errorForeground);\n}\n.void-scope .monaco-editor .error-detection-warning {\n background-color: color-mix(in srgb, var(--vscode-warningForeground) 10%, transparent);\n border-left: 3px solid var(--vscode-warningForeground);\n}\n.void-scope .monaco-editor .error-detection-glyph-error::before {\n content: "\\274c";\n font-size: 14px;\n color: var(--vscode-errorForeground);\n}\n.void-scope .monaco-editor .error-detection-glyph-warning::before {\n content: "\\26a0\\fe0f";\n font-size: 14px;\n color: var(--vscode-warningForeground);\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n font-size: 0.875rem;\n line-height: 1.7142857;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(p):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n margin-bottom: 1.1428571em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where([class~=lead]):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 1.2857143em;\n line-height: 1.5555556;\n margin-top: 0.8888889em;\n margin-bottom: 0.8888889em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(blockquote):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.3333333em;\n margin-bottom: 1.3333333em;\n padding-inline-start: 1.1111111em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(h1):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 2.1428571em;\n margin-top: 0;\n margin-bottom: 0.8em;\n line-height: 1.2;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(h2):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 1.4285714em;\n margin-top: 1.6em;\n margin-bottom: 0.8em;\n line-height: 1.4;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(h3):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 1.2857143em;\n margin-top: 1.5555556em;\n margin-bottom: 0.4444444em;\n line-height: 1.5555556;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(h4):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.4285714em;\n margin-bottom: 0.5714286em;\n line-height: 1.4285714;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(img):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.7142857em;\n margin-bottom: 1.7142857em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(picture):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.7142857em;\n margin-bottom: 1.7142857em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(picture > img):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n margin-bottom: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(video):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.7142857em;\n margin-bottom: 1.7142857em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(kbd):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8571429em;\n border-radius: 0.3125rem;\n padding-top: 0.1428571em;\n padding-inline-end: 0.3571429em;\n padding-bottom: 0.1428571em;\n padding-inline-start: 0.3571429em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8571429em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(h2 code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.9em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(h3 code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8888889em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(pre):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8571429em;\n line-height: 1.6666667;\n margin-top: 1.6666667em;\n margin-bottom: 1.6666667em;\n border-radius: 0.25rem;\n padding-top: 0.6666667em;\n padding-inline-end: 1em;\n padding-bottom: 0.6666667em;\n padding-inline-start: 1em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n margin-bottom: 1.1428571em;\n padding-inline-start: 1.5714286em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(ul):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n margin-bottom: 1.1428571em;\n padding-inline-start: 1.5714286em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(li):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.2857143em;\n margin-bottom: 0.2857143em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(ol > li):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0.4285714em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(ul > li):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0.4285714em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(.prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) > ul > li p):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.5714286em;\n margin-bottom: 0.5714286em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(.prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) > ul > li > p:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(.prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) > ul > li > p:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-bottom: 1.1428571em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(.prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) > ol > li > p:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(.prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) > ol > li > p:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-bottom: 1.1428571em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.5714286em;\n margin-bottom: 0.5714286em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(dl):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n margin-bottom: 1.1428571em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(dt):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.1428571em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(dd):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0.2857143em;\n padding-inline-start: 1.5714286em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(hr):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 2.8571429em;\n margin-bottom: 2.8571429em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(hr + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(h2 + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(h3 + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(h4 + *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(table):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8571429em;\n line-height: 1.5;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(thead th):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-end: 1em;\n padding-bottom: 0.6666667em;\n padding-inline-start: 1em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(thead th:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(thead th:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-end: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(tbody td, tfoot td):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-top: 0.6666667em;\n padding-inline-end: 1em;\n padding-bottom: 0.6666667em;\n padding-inline-start: 1em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(tbody td:first-child, tfoot td:first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-start: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(tbody td:last-child, tfoot td:last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n padding-inline-end: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(figure):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 1.7142857em;\n margin-bottom: 1.7142857em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(figure > *):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n margin-bottom: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(figcaption):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n font-size: 0.8571429em;\n line-height: 1.3333333;\n margin-top: 0.6666667em;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(.prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) > :first-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-top: 0;\n}\n.void-scope .prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) :where(.prose-headings\\:void-prose-sm :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) > :last-child):not(:where([class~=void-not-prose], [class~=void-not-prose] *)) {\n margin-bottom: 0;\n}\n.void-scope .marker\\:void-text-inherit *::marker {\n color: inherit;\n}\n.void-scope .marker\\:void-text-inherit::marker {\n color: inherit;\n}\n.void-scope .placeholder\\:void-text-void-fg-3::-moz-placeholder {\n color: var(--void-fg-3);\n}\n.void-scope .placeholder\\:void-text-void-fg-3::placeholder {\n color: var(--void-fg-3);\n}\n.void-scope .focus-within\\:void-border-\\[rgba\\(255\\,255\\,255\\,0\\.12\\)\\]:focus-within {\n border-color: rgba(255, 255, 255, 0.12);\n}\n.void-scope .hover\\:-void-translate-y-0\\.5:hover {\n --tw-translate-y: -0.125rem;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .hover\\:void-translate-y-\\[-1px\\]:hover {\n --tw-translate-y: -1px;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .hover\\:void-cursor-pointer:hover {\n cursor: pointer;\n}\n.void-scope .hover\\:void-border-\\[rgba\\(255\\,255\\,255\\,0\\.12\\)\\]:hover {\n border-color: rgba(255, 255, 255, 0.12);\n}\n.void-scope .hover\\:void-border-void-border-1:hover {\n border-color: var(--void-border-1);\n}\n.void-scope .hover\\:void-border-void-border-3:hover {\n border-color: var(--void-border-3);\n}\n.void-scope .hover\\:void-border-white\\/40:hover {\n border-color: rgb(255 255 255 / 0.4);\n}\n.void-scope .hover\\:void-bg-\\[\\#1177cb\\]:hover {\n --tw-bg-opacity: 1;\n background-color: rgb(17 119 203 / var(--tw-bg-opacity, 1));\n}\n.void-scope .hover\\:void-bg-\\[var\\(--vscode-button-hoverBackground\\)\\]:hover {\n background-color: var(--vscode-button-hoverBackground);\n}\n.void-scope .hover\\:void-bg-\\[var\\(--vscode-button-secondaryHoverBackground\\)\\]:hover {\n background-color: var(--vscode-button-secondaryHoverBackground);\n}\n.void-scope .hover\\:void-bg-black\\/10:hover {\n background-color: rgb(0 0 0 / 0.1);\n}\n.void-scope .hover\\:void-bg-black\\/80:hover {\n background-color: rgb(0 0 0 / 0.8);\n}\n.void-scope .hover\\:void-bg-blue-500:hover {\n --tw-bg-opacity: 1;\n background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));\n}\n.void-scope .hover\\:void-bg-gray-500\\/20:hover {\n background-color: rgb(107 114 128 / 0.2);\n}\n.void-scope .hover\\:void-bg-green-500\\/20:hover {\n background-color: rgb(34 197 94 / 0.2);\n}\n.void-scope .hover\\:void-bg-orange-500\\/20:hover {\n background-color: rgb(249 115 22 / 0.2);\n}\n.void-scope .hover\\:void-bg-red-50:hover {\n --tw-bg-opacity: 1;\n background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));\n}\n.void-scope .hover\\:void-bg-red-500\\/20:hover {\n background-color: rgb(239 68 68 / 0.2);\n}\n.void-scope .hover\\:void-bg-red-600:hover {\n --tw-bg-opacity: 1;\n background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1));\n}\n.void-scope .hover\\:void-bg-red-700:hover {\n --tw-bg-opacity: 1;\n background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1));\n}\n.void-scope .hover\\:void-bg-void-bg-2:hover {\n background-color: var(--void-bg-2);\n}\n.void-scope .hover\\:void-bg-void-bg-2-alt:hover {\n background-color: var(--void-bg-2-alt);\n}\n.void-scope .hover\\:void-bg-void-bg-3:hover {\n background-color: var(--void-bg-3);\n}\n.void-scope .hover\\:void-bg-white\\/10:hover {\n background-color: rgb(255 255 255 / 0.1);\n}\n.void-scope .hover\\:void-bg-white\\/60:hover {\n background-color: rgb(255 255 255 / 0.6);\n}\n.void-scope .hover\\:void-bg-zinc-700\\/10:hover {\n background-color: rgb(63 63 70 / 0.1);\n}\n.void-scope .hover\\:void-text-blue-300:hover {\n --tw-text-opacity: 1;\n color: rgb(147 197 253 / var(--tw-text-opacity, 1));\n}\n.void-scope .hover\\:void-text-blue-400:hover {\n --tw-text-opacity: 1;\n color: rgb(96 165 250 / var(--tw-text-opacity, 1));\n}\n.void-scope .hover\\:void-text-red-800:hover {\n --tw-text-opacity: 1;\n color: rgb(153 27 27 / var(--tw-text-opacity, 1));\n}\n.void-scope .hover\\:void-text-void-fg-0:hover {\n color: var(--void-fg-0);\n}\n.void-scope .hover\\:void-text-void-fg-1:hover {\n color: var(--void-fg-1);\n}\n.void-scope .hover\\:void-text-void-fg-2:hover {\n color: var(--void-fg-2);\n}\n.void-scope .hover\\:void-text-white:hover {\n --tw-text-opacity: 1;\n color: rgb(255 255 255 / var(--tw-text-opacity, 1));\n}\n.void-scope .hover\\:void-text-white\\/80:hover {\n color: rgb(255 255 255 / 0.8);\n}\n.void-scope .hover\\:void-opacity-100:hover {\n opacity: 1;\n}\n.void-scope .hover\\:void-opacity-90:hover {\n opacity: 0.9;\n}\n.void-scope .hover\\:void-shadow-\\[0_30px_70px_rgba\\(0\\,0\\,0\\,0\\.65\\)\\]:hover {\n --tw-shadow: 0 30px 70px rgba(0,0,0,0.65);\n --tw-shadow-colored: 0 30px 70px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .hover\\:void-shadow-\\[0_45px_100px_rgba\\(0\\,0\\,0\\,0\\.7\\)\\]:hover {\n --tw-shadow: 0 45px 100px rgba(0,0,0,0.7);\n --tw-shadow-colored: 0 45px 100px var(--tw-shadow-color);\n box-shadow:\n var(--tw-ring-offset-shadow, 0 0 #0000),\n var(--tw-ring-shadow, 0 0 #0000),\n var(--tw-shadow);\n}\n.void-scope .hover\\:void-brightness-110:hover {\n --tw-brightness: brightness(1.1);\n filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\n}\n.void-scope .hover\\:void-brightness-125:hover {\n --tw-brightness: brightness(1.25);\n filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\n}\n.void-scope .hover\\:void-brightness-75:hover {\n --tw-brightness: brightness(.75);\n filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\n}\n.void-scope .hover\\:void-brightness-90:hover {\n --tw-brightness: brightness(.9);\n filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\n}\n.void-scope .hover\\:void-brightness-95:hover {\n --tw-brightness: brightness(.95);\n filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\n}\n.void-scope .focus\\:void-border-void-border-1:focus {\n border-color: var(--void-border-1);\n}\n.void-scope .focus\\:void-outline-none:focus {\n outline: 2px solid transparent;\n outline-offset: 2px;\n}\n.void-scope .focus\\:void-ring-2:focus {\n --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n box-shadow:\n var(--tw-ring-offset-shadow),\n var(--tw-ring-shadow),\n var(--tw-shadow, 0 0 #0000);\n}\n.void-scope .focus\\:void-ring-green-500\\/40:focus {\n --tw-ring-color: rgb(34 197 94 / 0.4);\n}\n.void-scope .focus\\:void-ring-orange-500\\/40:focus {\n --tw-ring-color: rgb(249 115 22 / 0.4);\n}\n.void-scope .focus\\:void-ring-red-500\\/40:focus {\n --tw-ring-color: rgb(239 68 68 / 0.4);\n}\n.void-scope .focus-visible\\:void-ring-2:focus-visible {\n --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);\n box-shadow:\n var(--tw-ring-offset-shadow),\n var(--tw-ring-shadow),\n var(--tw-shadow, 0 0 #0000);\n}\n.void-scope .focus-visible\\:void-ring-white\\/20:focus-visible {\n --tw-ring-color: rgb(255 255 255 / 0.2);\n}\n.void-scope .focus-visible\\:void-ring-offset-2:focus-visible {\n --tw-ring-offset-width: 2px;\n}\n.void-scope .focus-visible\\:void-ring-offset-\\[\\#050612\\]:focus-visible {\n --tw-ring-offset-color: #050612;\n}\n.void-scope .active\\:void-scale-\\[0\\.98\\]:active {\n --tw-scale-x: 0.98;\n --tw-scale-y: 0.98;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .active\\:void-cursor-grabbing:active {\n cursor: grabbing;\n}\n.void-scope .disabled\\:void-cursor-not-allowed:disabled {\n cursor: not-allowed;\n}\n.void-scope .disabled\\:void-opacity-40:disabled {\n opacity: 0.4;\n}\n.void-scope .disabled\\:void-opacity-50:disabled {\n opacity: 0.5;\n}\n.void-scope .disabled\\:void-opacity-60:disabled {\n opacity: 0.6;\n}\n.void-scope .void-group:hover .group-hover\\:void-scale-\\[1\\.02\\] {\n --tw-scale-x: 1.02;\n --tw-scale-y: 1.02;\n transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));\n}\n.void-scope .void-group:hover .group-hover\\:void-border-void-border-1 {\n border-color: var(--void-border-1);\n}\n.void-scope .void-group:hover .group-hover\\:void-text-blue-300 {\n --tw-text-opacity: 1;\n color: rgb(147 197 253 / var(--tw-text-opacity, 1));\n}\n.void-scope .void-group:hover .group-hover\\:void-text-void-fg-3 {\n color: var(--void-fg-3);\n}\n.void-scope .void-group:hover .group-hover\\:void-opacity-100 {\n opacity: 1;\n}\n.void-scope .prose-headings\\:void-font-bold :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n font-weight: 700;\n}\n.void-scope .prose-h1\\:void-my-4 :is(:where(h1):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 1rem;\n margin-bottom: 1rem;\n}\n.void-scope .prose-h1\\:void-text-\\[14px\\] :is(:where(h1):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n font-size: 14px;\n}\n.void-scope .prose-h2\\:void-my-4 :is(:where(h2):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 1rem;\n margin-bottom: 1rem;\n}\n.void-scope .prose-h2\\:void-text-\\[13px\\] :is(:where(h2):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n font-size: 13px;\n}\n.void-scope .prose-h3\\:void-my-3 :is(:where(h3):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0.75rem;\n margin-bottom: 0.75rem;\n}\n.void-scope .prose-h3\\:void-text-\\[13px\\] :is(:where(h3):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n font-size: 13px;\n}\n.void-scope .prose-h4\\:void-my-2 :is(:where(h4):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n}\n.void-scope .prose-h4\\:void-text-\\[13px\\] :is(:where(h4):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n font-size: 13px;\n}\n.void-scope .prose-p\\:void-my-0 :is(:where(p):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0px;\n margin-bottom: 0px;\n}\n.void-scope .prose-p\\:void-my-2 :is(:where(p):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n}\n.void-scope .prose-p\\:void-block :is(:where(p):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n display: block;\n}\n.void-scope .prose-p\\:void-py-0 :is(:where(p):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n padding-top: 0px;\n padding-bottom: 0px;\n}\n.void-scope .prose-p\\:void-leading-normal :is(:where(p):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n line-height: 1.5;\n}\n.void-scope .prose-p\\:void-leading-snug :is(:where(p):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n line-height: 1.375;\n}\n.void-scope .prose-blockquote\\:void-my-2 :is(:where(blockquote):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n}\n.void-scope .prose-blockquote\\:void-pl-2 :is(:where(blockquote):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n padding-left: 0.5rem;\n}\n.void-scope .prose-code\\:void-text-\\[12px\\] :is(:where(code):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n font-size: 12px;\n}\n.void-scope .prose-code\\:void-text-void-fg-3 :is(:where(code):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n color: var(--void-fg-3);\n}\n.void-scope .prose-code\\:before\\:void-content-none :is(:where(code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)))::before {\n --tw-content: none;\n content: var(--tw-content);\n}\n.void-scope .prose-code\\:after\\:void-content-none :is(:where(code):not(:where([class~=void-not-prose], [class~=void-not-prose] *)))::after {\n --tw-content: none;\n content: var(--tw-content);\n}\n.void-scope .prose-pre\\:void-my-2 :is(:where(pre):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n}\n.void-scope .prose-pre\\:void-p-2 :is(:where(pre):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n padding: 0.5rem;\n}\n.void-scope .prose-pre\\:void-text-\\[12px\\] :is(:where(pre):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n font-size: 12px;\n}\n.void-scope .prose-ol\\:void-my-0 :is(:where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0px;\n margin-bottom: 0px;\n}\n.void-scope .prose-ol\\:void-my-2 :is(:where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n}\n.void-scope .prose-ol\\:void-list-outside :is(:where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n list-style-position: outside;\n}\n.void-scope .prose-ol\\:void-list-decimal :is(:where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n list-style-type: decimal;\n}\n.void-scope .prose-ol\\:void-py-0 :is(:where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n padding-top: 0px;\n padding-bottom: 0px;\n}\n.void-scope .prose-ol\\:void-pl-4 :is(:where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n padding-left: 1rem;\n}\n.void-scope .prose-ol\\:void-leading-normal :is(:where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n line-height: 1.5;\n}\n.void-scope .prose-ol\\:void-leading-snug :is(:where(ol):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n line-height: 1.375;\n}\n.void-scope .prose-ul\\:void-my-2 :is(:where(ul):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n}\n.void-scope .prose-ul\\:void-list-outside :is(:where(ul):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n list-style-position: outside;\n}\n.void-scope .prose-ul\\:void-list-disc :is(:where(ul):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n list-style-type: disc;\n}\n.void-scope .prose-ul\\:void-pl-4 :is(:where(ul):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n padding-left: 1rem;\n}\n.void-scope .prose-ul\\:void-leading-normal :is(:where(ul):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n line-height: 1.5;\n}\n.void-scope .prose-ul\\:void-leading-snug :is(:where(ul):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n line-height: 1.375;\n}\n.void-scope .prose-li\\:void-my-0 :is(:where(li):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0px;\n margin-bottom: 0px;\n}\n.void-scope .prose-table\\:void-text-\\[13px\\] :is(:where(table):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n font-size: 13px;\n}\n.void-scope .prose-hr\\:void-my-2 :is(:where(hr):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n}\n.void-scope .prose-hr\\:void-my-4 :is(:where(hr):not(:where([class~=void-not-prose], [class~=void-not-prose] *))) {\n margin-top: 1rem;\n margin-bottom: 1rem;\n}\n@media (min-width: 768px) {\n .void-scope .md\\:void-mx-0 {\n margin-left: 0px;\n margin-right: 0px;\n }\n .void-scope .md\\:void-max-h-\\[300px\\] {\n max-height: 300px;\n }\n .void-scope .md\\:void-max-h-\\[400px\\] {\n max-height: 400px;\n }\n .void-scope .md\\:void-w-1\\/3 {\n width: 33.333333%;\n }\n .void-scope .md\\:void-w-1\\/4 {\n width: 25%;\n }\n .void-scope .md\\:void-flex-row {\n flex-direction: row;\n }\n .void-scope .md\\:void-flex-col {\n flex-direction: column;\n }\n .void-scope .md\\:void-text-left {\n text-align: left;\n }\n}\n@media (min-width: 1024px) {\n .void-scope .lg\\:void-mx-0 {\n margin-left: 0px;\n margin-right: 0px;\n }\n .void-scope .lg\\:void-flex-row {\n flex-direction: row;\n }\n .void-scope .lg\\:void-justify-start {\n justify-content: flex-start;\n }\n .void-scope .lg\\:void-text-left {\n text-align: left;\n }\n}\n.void-scope .dark\\:void-bg-white:where(.void-dark, .void-dark *) {\n --tw-bg-opacity: 1;\n background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));\n}\n.void-scope .dark\\:void-bg-white\\/10:where(.void-dark, .void-dark *) {\n background-color: rgb(255 255 255 / 0.1);\n}\n.void-scope .dark\\:void-bg-zinc-300\\/5:where(.void-dark, .void-dark *) {\n background-color: rgb(212 212 216 / 0.05);\n}\n.void-scope .dark\\:void-bg-zinc-600:where(.void-dark, .void-dark *) {\n --tw-bg-opacity: 1;\n background-color: rgb(82 82 91 / var(--tw-bg-opacity, 1));\n}\n.void-scope .dark\\:void-bg-zinc-900:where(.void-dark, .void-dark *) {\n --tw-bg-opacity: 1;\n background-color: rgb(24 24 27 / var(--tw-bg-opacity, 1));\n}\n.void-scope .dark\\:hover\\:void-bg-gray-300\\/10:hover:where(.void-dark, .void-dark *) {\n background-color: rgb(209 213 219 / 0.1);\n}\n.void-scope .dark\\:hover\\:void-bg-zinc-300\\/10:hover:where(.void-dark, .void-dark *) {\n background-color: rgb(212 212 216 / 0.1);\n}\n.void-scope .\\[\\&\\>\\*\\:first-child\\]\\:void-pl-3 > *:first-child {\n padding-left: 0.75rem;\n}\n.void-scope .\\[\\&\\>\\*\\:last-child\\]\\:void-border-r-0 > *:last-child {\n border-right-width: 0px;\n}\n.void-scope .\\[\\&\\>\\*\\:last-child\\]\\:void-pr-3 > *:last-child {\n padding-right: 0.75rem;\n}\n.void-scope .\\[\\&\\>\\*\\]\\:void-border-r > * {\n border-right-width: 1px;\n}\n.void-scope .\\[\\&\\>\\*\\]\\:void-border-void-border-2 > * {\n border-color: var(--void-border-2);\n}\n.void-scope .\\[\\&\\>\\*\\]\\:void-px-3 > * {\n padding-left: 0.75rem;\n padding-right: 0.75rem;\n}\n.void-scope .\\[\\&\\>\\:first-child\\]\\:\\!void-mt-0 > :first-child {\n margin-top: 0px !important;\n}\n.void-scope .\\[\\&\\>\\:last-child\\]\\:\\!void-mb-0 > :last-child {\n margin-bottom: 0px !important;\n}\n.void-scope .\\[\\&_select\\]\\:\\!void-text-xs select {\n font-size: 10px !important;\n}\n.void-scope .\\[\\&_select\\]\\:\\!void-text-void-fg-3 select {\n color: var(--void-fg-3) !important;\n}\n'); + +export { styleInject }; diff --git a/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-GKOTMYUK.js b/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-GKOTMYUK.js new file mode 100644 index 00000000000..d1df29d61bf --- /dev/null +++ b/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-GKOTMYUK.js @@ -0,0 +1,11930 @@ +import { ChevronRight, CircleEllipsis, Check, X, File, TriangleAlert, Ban, Dot, Pencil, CirclePlus, Info, Folder, Text, FileText, ChevronLeft, FileSymlink, Copy, Play, Square, Image as Image$1, Plus, Asterisk, CircleAlert, ChevronUp, ChevronDown, RefreshCw, RotateCcw, LoaderCircle, MessageCircleQuestion, Trash2 } from './chunk-PT4A2IRQ.js'; +import { offset, flip, shift, size, isElement, autoUpdate, computePosition } from './chunk-SWVXQVDT.js'; +import { require_react, require_jsx_runtime, require_react_dom, useAccessor, useChatThreadsState, useChatThreadsStreamState, useSettingsState, useFullChatThreadsStreamState, useActiveURI, useCommandBarURIListener, useIsDark, useIsOptedOut, useCommandBarState, useMCPServiceState, useRefreshModelState, useRefreshModelListener } from './chunk-RJP66NWB.js'; +import { __toESM } from './chunk-JSBRDJBE.js'; +import { isFeatureNameDisabled, providerNames, displayInfoOfProviderName, isProviderNameDisabled, customSettingNamesOfProvider, subTextMdOfProviderName, localProviderNames, nonlocalProviderNames, displayInfoOfFeatureName, displayInfoOfSettingName, refreshableProviderNames } from 'vs/workbench/contrib/cortexide/common/cortexideSettingsTypes.js'; +import { ScrollType } from 'vs/editor/common/editorCommon.js'; +import { convertToVscodeLang, detectLanguage } from 'vs/workbench/contrib/cortexide/common/helpers/languageHelpers.js'; +import { URI } from 'vs/base/common/uri.js'; +import { isAbsolute } from 'vs/base/common/path.js'; +import { separateOutFirstLine } from 'vs/workbench/contrib/cortexide/common/helpers/util.js'; +import 'vs/base/browser/ui/inputbox/inputBox.js'; +import 'vs/platform/theme/browser/defaultStyles.js'; +import 'vs/base/browser/ui/selectBox/selectBox.js'; +import 'vs/base/browser/ui/toggle/toggle.js'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget.js'; +import { asCssVariable } from 'vs/platform/theme/common/colorUtils.js'; +import { inputForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry.js'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget.js'; +import { extractSearchReplaceBlocks } from 'vs/workbench/contrib/cortexide/common/helpers/extractCodeFromResult.js'; +import { errorDetails } from 'vs/workbench/contrib/cortexide/common/sendLLMMessageTypes.js'; +import { toErrorMessage } from 'vs/base/common/errorMessage.js'; +import { isFeatureNameDisabled as isFeatureNameDisabled$1, isValidProviderModelSelection, modelSelectionsEqual } from 'vs/workbench/contrib/cortexide/common/cortexideSettingsTypes.js'; +import { CORTEXIDE_OPEN_SETTINGS_ACTION_ID, CORTEXIDE_CTRL_L_ACTION_ID } from 'vs/workbench/contrib/cortexide/browser/actionIDs.js'; +import { modelFilterOfFeatureName } from 'vs/workbench/contrib/cortexide/common/cortexideSettingsService.js'; +import { getModelCapabilities, getIsReasoningEnabledState, getReservedOutputTokenSpace, modelOverrideKeys } from 'vs/workbench/contrib/cortexide/common/modelCapabilities.js'; +import { approvalTypeOfBuiltinToolName, toolApprovalTypes } from 'vs/workbench/contrib/cortexide/common/toolsServiceTypes.js'; +import { isABuiltinToolName, builtinToolNames, MAX_FILE_CHARS_PAGE } from 'vs/workbench/contrib/cortexide/common/prompt/prompts.js'; +import { persistentTerminalNameOfId } from 'vs/workbench/contrib/cortexide/browser/terminalToolService.js'; +import { removeMCPToolNamePrefix } from 'vs/workbench/contrib/cortexide/common/mcpServiceTypes.js'; +import { getPDFService } from 'vs/workbench/contrib/cortexide/common/pdfService.js'; +import { os } from 'vs/workbench/contrib/cortexide/common/helpers/systemInfo.js'; +import Severity from 'vs/base/common/severity.js'; +import { OPT_OUT_KEY } from 'vs/workbench/contrib/cortexide/common/storageKeys.js'; +import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage.js'; + +// src2/void-settings-tsx/Settings.tsx +var import_react21 = __toESM(require_react(), 1); + +// src2/sidebar-tsx/ErrorBoundary.tsx +var import_react20 = __toESM(require_react(), 1); + +// src2/sidebar-tsx/SidebarChat.tsx +var import_react19 = __toESM(require_react(), 1); + +// src2/markdown/ChatMarkdownRender.tsx +var import_react7 = __toESM(require_react(), 1); + +// ../../../../../../../node_modules/marked/lib/marked.esm.js +function _getDefaults() { + return { + async: false, + breaks: false, + extensions: null, + gfm: true, + hooks: null, + pedantic: false, + renderer: null, + silent: false, + tokenizer: null, + walkTokens: null + }; +} +var _defaults = _getDefaults(); +function changeDefaults(newDefaults) { + _defaults = newDefaults; +} +var noopTest = { exec: () => null }; +function edit(regex, opt = "") { + let source = typeof regex === "string" ? regex : regex.source; + const obj = { + replace: (name, val) => { + let valSource = typeof val === "string" ? val : val.source; + valSource = valSource.replace(other.caret, "$1"); + source = source.replace(name, valSource); + return obj; + }, + getRegex: () => { + return new RegExp(source, opt); + } + }; + return obj; +} +var other = { + codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, + outputLinkReplace: /\\([\[\]])/g, + indentCodeCompensation: /^(\s+)(?:```)/, + beginningSpace: /^\s+/, + endingHash: /#$/, + startingSpaceChar: /^ /, + endingSpaceChar: / $/, + nonSpaceChar: /[^ ]/, + newLineCharGlobal: /\n/g, + tabCharGlobal: /\t/g, + multipleSpaceGlobal: /\s+/g, + blankLine: /^[ \t]*$/, + doubleBlankLine: /\n[ \t]*\n[ \t]*$/, + blockquoteStart: /^ {0,3}>/, + blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, + blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, + listReplaceTabs: /^\t+/, + listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, + listIsTask: /^\[[ xX]\] /, + listReplaceTask: /^\[[ xX]\] +/, + anyLine: /\n.*\n/, + hrefBrackets: /^<(.*)>$/, + tableDelimiter: /[:|]/, + tableAlignChars: /^\||\| *$/g, + tableRowBlankLine: /\n[ \t]*$/, + tableAlignRight: /^ *-+: *$/, + tableAlignCenter: /^ *:-+: *$/, + tableAlignLeft: /^ *:-+ *$/, + startATag: /^/i, + startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, + endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, + startAngleBracket: /^$/, + pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, + unicodeAlphaNumeric: /[\p{L}\p{N}]/u, + escapeTest: /[&<>"']/, + escapeReplace: /[&<>"']/g, + escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, + escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, + unescapeTest: /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, + caret: /(^|[^\[])\^/g, + percentDecode: /%25/g, + findPipe: /\|/g, + splitPipe: / \|/, + slashPipe: /\\\|/g, + carriageReturn: /\r\n|\r/g, + spaceLine: /^ +$/gm, + notSpaceStart: /^\S*/, + endingNewline: /\n$/, + listItemRegex: (bull) => new RegExp(`^( {0,3}${bull})((?:[ ][^\\n]*)?(?:\\n|$))`), + nextBulletRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`), + hrRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`), + fencesBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`), + headingBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`), + htmlBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}<(?:[a-z].*>|!--)`, "i") +}; +var newline = /^(?:[ \t]*(?:\n|$))+/; +var blockCode = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/; +var fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/; +var hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/; +var heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/; +var bullet = /(?:[*+-]|\d{1,9}[.)])/; +var lheadingCore = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/; +var lheading = edit(lheadingCore).replace(/bull/g, bullet).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex(); +var lheadingGfm = edit(lheadingCore).replace(/bull/g, bullet).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(); +var _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/; +var blockText = /^[^\n]+/; +var _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/; +var def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", _blockLabel).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(); +var list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g, bullet).getRegex(); +var _tag = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul"; +var _comment = /|$))/; +var html = edit( + "^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", + "i" +).replace("comment", _comment).replace("tag", _tag).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(); +var paragraph = edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex(); +var blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", paragraph).getRegex(); +var blockNormal = { + blockquote, + code: blockCode, + def, + fences, + heading, + hr, + html, + lheading, + list, + newline, + paragraph, + table: noopTest, + text: blockText +}; +var gfmTable = edit( + "^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)" +).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex(); +var blockGfm = { + ...blockNormal, + lheading: lheadingGfm, + table: gfmTable, + paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", gfmTable).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex() +}; +var blockPedantic = { + ...blockNormal, + html: edit( + `^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))` + ).replace("comment", _comment).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), + def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, + heading: /^(#{1,6})(.*)(?:\n+|$)/, + fences: noopTest, + // fences not supported + lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, + paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " *#{1,6} *[^\n]").replace("lheading", lheading).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() +}; +var escape = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/; +var inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/; +var br = /^( {2,}|\\)\n(?!\s*$)/; +var inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\]*?>/g; +var emStrongLDelimCore = /^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/; +var emStrongLDelim = edit(emStrongLDelimCore, "u").replace(/punct/g, _punctuation).getRegex(); +var emStrongLDelimGfm = edit(emStrongLDelimCore, "u").replace(/punct/g, _punctuationGfmStrongEm).getRegex(); +var emStrongRDelimAstCore = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)"; +var emStrongRDelimAst = edit(emStrongRDelimAstCore, "gu").replace(/notPunctSpace/g, _notPunctuationOrSpace).replace(/punctSpace/g, _punctuationOrSpace).replace(/punct/g, _punctuation).getRegex(); +var emStrongRDelimAstGfm = edit(emStrongRDelimAstCore, "gu").replace(/notPunctSpace/g, _notPunctuationOrSpaceGfmStrongEm).replace(/punctSpace/g, _punctuationOrSpaceGfmStrongEm).replace(/punct/g, _punctuationGfmStrongEm).getRegex(); +var emStrongRDelimUnd = edit( + "^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)", + "gu" +).replace(/notPunctSpace/g, _notPunctuationOrSpace).replace(/punctSpace/g, _punctuationOrSpace).replace(/punct/g, _punctuation).getRegex(); +var anyPunctuation = edit(/\\(punct)/, "gu").replace(/punct/g, _punctuation).getRegex(); +var autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(); +var _inlineComment = edit(_comment).replace("(?:-->|$)", "-->").getRegex(); +var tag = edit( + "^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^" +).replace("comment", _inlineComment).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(); +var _inlineLabel = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/; +var link = edit(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label", _inlineLabel).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(); +var reflink = edit(/^!?\[(label)\]\[(ref)\]/).replace("label", _inlineLabel).replace("ref", _blockLabel).getRegex(); +var nolink = edit(/^!?\[(ref)\](?:\[\])?/).replace("ref", _blockLabel).getRegex(); +var reflinkSearch = edit("reflink|nolink(?!\\()", "g").replace("reflink", reflink).replace("nolink", nolink).getRegex(); +var inlineNormal = { + _backpedal: noopTest, + // only used for GFM url + anyPunctuation, + autolink, + blockSkip, + br, + code: inlineCode, + del: noopTest, + emStrongLDelim, + emStrongRDelimAst, + emStrongRDelimUnd, + escape, + link, + nolink, + punctuation, + reflink, + reflinkSearch, + tag, + text: inlineText, + url: noopTest +}; +var inlinePedantic = { + ...inlineNormal, + link: edit(/^!?\[(label)\]\((.*?)\)/).replace("label", _inlineLabel).getRegex(), + reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", _inlineLabel).getRegex() +}; +var inlineGfm = { + ...inlineNormal, + emStrongRDelimAst: emStrongRDelimAstGfm, + emStrongLDelim: emStrongLDelimGfm, + url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, "i").replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(), + _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, + del: /^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/, + text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\": ">", + '"': """, + "'": "'" +}; +var getEscapeReplacement = (ch) => escapeReplacements[ch]; +function escape2(html2, encode) { + if (encode) { + if (other.escapeTest.test(html2)) { + return html2.replace(other.escapeReplace, getEscapeReplacement); + } + } else { + if (other.escapeTestNoEncode.test(html2)) { + return html2.replace(other.escapeReplaceNoEncode, getEscapeReplacement); + } + } + return html2; +} +function cleanUrl(href) { + try { + href = encodeURI(href).replace(other.percentDecode, "%"); + } catch { + return null; + } + return href; +} +function splitCells(tableRow, count2) { + const row = tableRow.replace(other.findPipe, (match, offset3, str) => { + let escaped = false; + let curr = offset3; + while (--curr >= 0 && str[curr] === "\\") escaped = !escaped; + if (escaped) { + return "|"; + } else { + return " |"; + } + }), cells = row.split(other.splitPipe); + let i = 0; + if (!cells[0].trim()) { + cells.shift(); + } + if (cells.length > 0 && !cells.at(-1)?.trim()) { + cells.pop(); + } + if (count2) { + if (cells.length > count2) { + cells.splice(count2); + } else { + while (cells.length < count2) cells.push(""); + } + } + for (; i < cells.length; i++) { + cells[i] = cells[i].trim().replace(other.slashPipe, "|"); + } + return cells; +} +function rtrim(str, c, invert) { + const l = str.length; + if (l === 0) { + return ""; + } + let suffLen = 0; + while (suffLen < l) { + const currChar = str.charAt(l - suffLen - 1); + if (currChar === c && true) { + suffLen++; + } else { + break; + } + } + return str.slice(0, l - suffLen); +} +function findClosingBracket(str, b) { + if (str.indexOf(b[1]) === -1) { + return -1; + } + let level = 0; + for (let i = 0; i < str.length; i++) { + if (str[i] === "\\") { + i++; + } else if (str[i] === b[0]) { + level++; + } else if (str[i] === b[1]) { + level--; + if (level < 0) { + return i; + } + } + } + if (level > 0) { + return -2; + } + return -1; +} +function outputLink(cap, link2, raw, lexer2, rules) { + const href = link2.href; + const title = link2.title || null; + const text = cap[1].replace(rules.other.outputLinkReplace, "$1"); + lexer2.state.inLink = true; + const token = { + type: cap[0].charAt(0) === "!" ? "image" : "link", + raw, + href, + title, + text, + tokens: lexer2.inlineTokens(text) + }; + lexer2.state.inLink = false; + return token; +} +function indentCodeCompensation(raw, text, rules) { + const matchIndentToCode = raw.match(rules.other.indentCodeCompensation); + if (matchIndentToCode === null) { + return text; + } + const indentToCode = matchIndentToCode[1]; + return text.split("\n").map((node) => { + const matchIndentInNode = node.match(rules.other.beginningSpace); + if (matchIndentInNode === null) { + return node; + } + const [indentInNode] = matchIndentInNode; + if (indentInNode.length >= indentToCode.length) { + return node.slice(indentToCode.length); + } + return node; + }).join("\n"); +} +var _Tokenizer = class { + options; + rules; + // set by the lexer + lexer; + // set by the lexer + constructor(options2) { + this.options = options2 || _defaults; + } + space(src) { + const cap = this.rules.block.newline.exec(src); + if (cap && cap[0].length > 0) { + return { + type: "space", + raw: cap[0] + }; + } + } + code(src) { + const cap = this.rules.block.code.exec(src); + if (cap) { + const text = cap[0].replace(this.rules.other.codeRemoveIndent, ""); + return { + type: "code", + raw: cap[0], + codeBlockStyle: "indented", + text: !this.options.pedantic ? rtrim(text, "\n") : text + }; + } + } + fences(src) { + const cap = this.rules.block.fences.exec(src); + if (cap) { + const raw = cap[0]; + const text = indentCodeCompensation(raw, cap[3] || "", this.rules); + return { + type: "code", + raw, + lang: cap[2] ? cap[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : cap[2], + text + }; + } + } + heading(src) { + const cap = this.rules.block.heading.exec(src); + if (cap) { + let text = cap[2].trim(); + if (this.rules.other.endingHash.test(text)) { + const trimmed = rtrim(text, "#"); + if (this.options.pedantic) { + text = trimmed.trim(); + } else if (!trimmed || this.rules.other.endingSpaceChar.test(trimmed)) { + text = trimmed.trim(); + } + } + return { + type: "heading", + raw: cap[0], + depth: cap[1].length, + text, + tokens: this.lexer.inline(text) + }; + } + } + hr(src) { + const cap = this.rules.block.hr.exec(src); + if (cap) { + return { + type: "hr", + raw: rtrim(cap[0], "\n") + }; + } + } + blockquote(src) { + const cap = this.rules.block.blockquote.exec(src); + if (cap) { + let lines = rtrim(cap[0], "\n").split("\n"); + let raw = ""; + let text = ""; + const tokens = []; + while (lines.length > 0) { + let inBlockquote = false; + const currentLines = []; + let i; + for (i = 0; i < lines.length; i++) { + if (this.rules.other.blockquoteStart.test(lines[i])) { + currentLines.push(lines[i]); + inBlockquote = true; + } else if (!inBlockquote) { + currentLines.push(lines[i]); + } else { + break; + } + } + lines = lines.slice(i); + const currentRaw = currentLines.join("\n"); + const currentText = currentRaw.replace(this.rules.other.blockquoteSetextReplace, "\n $1").replace(this.rules.other.blockquoteSetextReplace2, ""); + raw = raw ? `${raw} +${currentRaw}` : currentRaw; + text = text ? `${text} +${currentText}` : currentText; + const top = this.lexer.state.top; + this.lexer.state.top = true; + this.lexer.blockTokens(currentText, tokens, true); + this.lexer.state.top = top; + if (lines.length === 0) { + break; + } + const lastToken = tokens.at(-1); + if (lastToken?.type === "code") { + break; + } else if (lastToken?.type === "blockquote") { + const oldToken = lastToken; + const newText = oldToken.raw + "\n" + lines.join("\n"); + const newToken = this.blockquote(newText); + tokens[tokens.length - 1] = newToken; + raw = raw.substring(0, raw.length - oldToken.raw.length) + newToken.raw; + text = text.substring(0, text.length - oldToken.text.length) + newToken.text; + break; + } else if (lastToken?.type === "list") { + const oldToken = lastToken; + const newText = oldToken.raw + "\n" + lines.join("\n"); + const newToken = this.list(newText); + tokens[tokens.length - 1] = newToken; + raw = raw.substring(0, raw.length - lastToken.raw.length) + newToken.raw; + text = text.substring(0, text.length - oldToken.raw.length) + newToken.raw; + lines = newText.substring(tokens.at(-1).raw.length).split("\n"); + continue; + } + } + return { + type: "blockquote", + raw, + tokens, + text + }; + } + } + list(src) { + let cap = this.rules.block.list.exec(src); + if (cap) { + let bull = cap[1].trim(); + const isordered = bull.length > 1; + const list2 = { + type: "list", + raw: "", + ordered: isordered, + start: isordered ? +bull.slice(0, -1) : "", + loose: false, + items: [] + }; + bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`; + if (this.options.pedantic) { + bull = isordered ? bull : "[*+-]"; + } + const itemRegex = this.rules.other.listItemRegex(bull); + let endsWithBlankLine = false; + while (src) { + let endEarly = false; + let raw = ""; + let itemContents = ""; + if (!(cap = itemRegex.exec(src))) { + break; + } + if (this.rules.block.hr.test(src)) { + break; + } + raw = cap[0]; + src = src.substring(raw.length); + let line = cap[2].split("\n", 1)[0].replace(this.rules.other.listReplaceTabs, (t) => " ".repeat(3 * t.length)); + let nextLine = src.split("\n", 1)[0]; + let blankLine = !line.trim(); + let indent = 0; + if (this.options.pedantic) { + indent = 2; + itemContents = line.trimStart(); + } else if (blankLine) { + indent = cap[1].length + 1; + } else { + indent = cap[2].search(this.rules.other.nonSpaceChar); + indent = indent > 4 ? 1 : indent; + itemContents = line.slice(indent); + indent += cap[1].length; + } + if (blankLine && this.rules.other.blankLine.test(nextLine)) { + raw += nextLine + "\n"; + src = src.substring(nextLine.length + 1); + endEarly = true; + } + if (!endEarly) { + const nextBulletRegex = this.rules.other.nextBulletRegex(indent); + const hrRegex = this.rules.other.hrRegex(indent); + const fencesBeginRegex = this.rules.other.fencesBeginRegex(indent); + const headingBeginRegex = this.rules.other.headingBeginRegex(indent); + const htmlBeginRegex = this.rules.other.htmlBeginRegex(indent); + while (src) { + const rawLine = src.split("\n", 1)[0]; + let nextLineWithoutTabs; + nextLine = rawLine; + if (this.options.pedantic) { + nextLine = nextLine.replace(this.rules.other.listReplaceNesting, " "); + nextLineWithoutTabs = nextLine; + } else { + nextLineWithoutTabs = nextLine.replace(this.rules.other.tabCharGlobal, " "); + } + if (fencesBeginRegex.test(nextLine)) { + break; + } + if (headingBeginRegex.test(nextLine)) { + break; + } + if (htmlBeginRegex.test(nextLine)) { + break; + } + if (nextBulletRegex.test(nextLine)) { + break; + } + if (hrRegex.test(nextLine)) { + break; + } + if (nextLineWithoutTabs.search(this.rules.other.nonSpaceChar) >= indent || !nextLine.trim()) { + itemContents += "\n" + nextLineWithoutTabs.slice(indent); + } else { + if (blankLine) { + break; + } + if (line.replace(this.rules.other.tabCharGlobal, " ").search(this.rules.other.nonSpaceChar) >= 4) { + break; + } + if (fencesBeginRegex.test(line)) { + break; + } + if (headingBeginRegex.test(line)) { + break; + } + if (hrRegex.test(line)) { + break; + } + itemContents += "\n" + nextLine; + } + if (!blankLine && !nextLine.trim()) { + blankLine = true; + } + raw += rawLine + "\n"; + src = src.substring(rawLine.length + 1); + line = nextLineWithoutTabs.slice(indent); + } + } + if (!list2.loose) { + if (endsWithBlankLine) { + list2.loose = true; + } else if (this.rules.other.doubleBlankLine.test(raw)) { + endsWithBlankLine = true; + } + } + let istask = null; + let ischecked; + if (this.options.gfm) { + istask = this.rules.other.listIsTask.exec(itemContents); + if (istask) { + ischecked = istask[0] !== "[ ] "; + itemContents = itemContents.replace(this.rules.other.listReplaceTask, ""); + } + } + list2.items.push({ + type: "list_item", + raw, + task: !!istask, + checked: ischecked, + loose: false, + text: itemContents, + tokens: [] + }); + list2.raw += raw; + } + const lastItem = list2.items.at(-1); + if (lastItem) { + lastItem.raw = lastItem.raw.trimEnd(); + lastItem.text = lastItem.text.trimEnd(); + } else { + return; + } + list2.raw = list2.raw.trimEnd(); + for (let i = 0; i < list2.items.length; i++) { + this.lexer.state.top = false; + list2.items[i].tokens = this.lexer.blockTokens(list2.items[i].text, []); + if (!list2.loose) { + const spacers = list2.items[i].tokens.filter((t) => t.type === "space"); + const hasMultipleLineBreaks = spacers.length > 0 && spacers.some((t) => this.rules.other.anyLine.test(t.raw)); + list2.loose = hasMultipleLineBreaks; + } + } + if (list2.loose) { + for (let i = 0; i < list2.items.length; i++) { + list2.items[i].loose = true; + } + } + return list2; + } + } + html(src) { + const cap = this.rules.block.html.exec(src); + if (cap) { + const token = { + type: "html", + block: true, + raw: cap[0], + pre: cap[1] === "pre" || cap[1] === "script" || cap[1] === "style", + text: cap[0] + }; + return token; + } + } + def(src) { + const cap = this.rules.block.def.exec(src); + if (cap) { + const tag2 = cap[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal, " "); + const href = cap[2] ? cap[2].replace(this.rules.other.hrefBrackets, "$1").replace(this.rules.inline.anyPunctuation, "$1") : ""; + const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline.anyPunctuation, "$1") : cap[3]; + return { + type: "def", + tag: tag2, + raw: cap[0], + href, + title + }; + } + } + table(src) { + const cap = this.rules.block.table.exec(src); + if (!cap) { + return; + } + if (!this.rules.other.tableDelimiter.test(cap[2])) { + return; + } + const headers = splitCells(cap[1]); + const aligns = cap[2].replace(this.rules.other.tableAlignChars, "").split("|"); + const rows = cap[3]?.trim() ? cap[3].replace(this.rules.other.tableRowBlankLine, "").split("\n") : []; + const item = { + type: "table", + raw: cap[0], + header: [], + align: [], + rows: [] + }; + if (headers.length !== aligns.length) { + return; + } + for (const align of aligns) { + if (this.rules.other.tableAlignRight.test(align)) { + item.align.push("right"); + } else if (this.rules.other.tableAlignCenter.test(align)) { + item.align.push("center"); + } else if (this.rules.other.tableAlignLeft.test(align)) { + item.align.push("left"); + } else { + item.align.push(null); + } + } + for (let i = 0; i < headers.length; i++) { + item.header.push({ + text: headers[i], + tokens: this.lexer.inline(headers[i]), + header: true, + align: item.align[i] + }); + } + for (const row of rows) { + item.rows.push(splitCells(row, item.header.length).map((cell, i) => { + return { + text: cell, + tokens: this.lexer.inline(cell), + header: false, + align: item.align[i] + }; + })); + } + return item; + } + lheading(src) { + const cap = this.rules.block.lheading.exec(src); + if (cap) { + return { + type: "heading", + raw: cap[0], + depth: cap[2].charAt(0) === "=" ? 1 : 2, + text: cap[1], + tokens: this.lexer.inline(cap[1]) + }; + } + } + paragraph(src) { + const cap = this.rules.block.paragraph.exec(src); + if (cap) { + const text = cap[1].charAt(cap[1].length - 1) === "\n" ? cap[1].slice(0, -1) : cap[1]; + return { + type: "paragraph", + raw: cap[0], + text, + tokens: this.lexer.inline(text) + }; + } + } + text(src) { + const cap = this.rules.block.text.exec(src); + if (cap) { + return { + type: "text", + raw: cap[0], + text: cap[0], + tokens: this.lexer.inline(cap[0]) + }; + } + } + escape(src) { + const cap = this.rules.inline.escape.exec(src); + if (cap) { + return { + type: "escape", + raw: cap[0], + text: cap[1] + }; + } + } + tag(src) { + const cap = this.rules.inline.tag.exec(src); + if (cap) { + if (!this.lexer.state.inLink && this.rules.other.startATag.test(cap[0])) { + this.lexer.state.inLink = true; + } else if (this.lexer.state.inLink && this.rules.other.endATag.test(cap[0])) { + this.lexer.state.inLink = false; + } + if (!this.lexer.state.inRawBlock && this.rules.other.startPreScriptTag.test(cap[0])) { + this.lexer.state.inRawBlock = true; + } else if (this.lexer.state.inRawBlock && this.rules.other.endPreScriptTag.test(cap[0])) { + this.lexer.state.inRawBlock = false; + } + return { + type: "html", + raw: cap[0], + inLink: this.lexer.state.inLink, + inRawBlock: this.lexer.state.inRawBlock, + block: false, + text: cap[0] + }; + } + } + link(src) { + const cap = this.rules.inline.link.exec(src); + if (cap) { + const trimmedUrl = cap[2].trim(); + if (!this.options.pedantic && this.rules.other.startAngleBracket.test(trimmedUrl)) { + if (!this.rules.other.endAngleBracket.test(trimmedUrl)) { + return; + } + const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), "\\"); + if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) { + return; + } + } else { + const lastParenIndex = findClosingBracket(cap[2], "()"); + if (lastParenIndex === -2) { + return; + } + if (lastParenIndex > -1) { + const start = cap[0].indexOf("!") === 0 ? 5 : 4; + const linkLen = start + cap[1].length + lastParenIndex; + cap[2] = cap[2].substring(0, lastParenIndex); + cap[0] = cap[0].substring(0, linkLen).trim(); + cap[3] = ""; + } + } + let href = cap[2]; + let title = ""; + if (this.options.pedantic) { + const link2 = this.rules.other.pedanticHrefTitle.exec(href); + if (link2) { + href = link2[1]; + title = link2[3]; + } + } else { + title = cap[3] ? cap[3].slice(1, -1) : ""; + } + href = href.trim(); + if (this.rules.other.startAngleBracket.test(href)) { + if (this.options.pedantic && !this.rules.other.endAngleBracket.test(trimmedUrl)) { + href = href.slice(1); + } else { + href = href.slice(1, -1); + } + } + return outputLink(cap, { + href: href ? href.replace(this.rules.inline.anyPunctuation, "$1") : href, + title: title ? title.replace(this.rules.inline.anyPunctuation, "$1") : title + }, cap[0], this.lexer, this.rules); + } + } + reflink(src, links) { + let cap; + if ((cap = this.rules.inline.reflink.exec(src)) || (cap = this.rules.inline.nolink.exec(src))) { + const linkString = (cap[2] || cap[1]).replace(this.rules.other.multipleSpaceGlobal, " "); + const link2 = links[linkString.toLowerCase()]; + if (!link2) { + const text = cap[0].charAt(0); + return { + type: "text", + raw: text, + text + }; + } + return outputLink(cap, link2, cap[0], this.lexer, this.rules); + } + } + emStrong(src, maskedSrc, prevChar = "") { + let match = this.rules.inline.emStrongLDelim.exec(src); + if (!match) return; + if (match[3] && prevChar.match(this.rules.other.unicodeAlphaNumeric)) return; + const nextChar = match[1] || match[2] || ""; + if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) { + const lLength = [...match[0]].length - 1; + let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0; + const endReg = match[0][0] === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd; + endReg.lastIndex = 0; + maskedSrc = maskedSrc.slice(-1 * src.length + lLength); + while ((match = endReg.exec(maskedSrc)) != null) { + rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6]; + if (!rDelim) continue; + rLength = [...rDelim].length; + if (match[3] || match[4]) { + delimTotal += rLength; + continue; + } else if (match[5] || match[6]) { + if (lLength % 3 && !((lLength + rLength) % 3)) { + midDelimTotal += rLength; + continue; + } + } + delimTotal -= rLength; + if (delimTotal > 0) continue; + rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); + const lastCharLength = [...match[0]][0].length; + const raw = src.slice(0, lLength + match.index + lastCharLength + rLength); + if (Math.min(lLength, rLength) % 2) { + const text2 = raw.slice(1, -1); + return { + type: "em", + raw, + text: text2, + tokens: this.lexer.inlineTokens(text2) + }; + } + const text = raw.slice(2, -2); + return { + type: "strong", + raw, + text, + tokens: this.lexer.inlineTokens(text) + }; + } + } + } + codespan(src) { + const cap = this.rules.inline.code.exec(src); + if (cap) { + let text = cap[2].replace(this.rules.other.newLineCharGlobal, " "); + const hasNonSpaceChars = this.rules.other.nonSpaceChar.test(text); + const hasSpaceCharsOnBothEnds = this.rules.other.startingSpaceChar.test(text) && this.rules.other.endingSpaceChar.test(text); + if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) { + text = text.substring(1, text.length - 1); + } + return { + type: "codespan", + raw: cap[0], + text + }; + } + } + br(src) { + const cap = this.rules.inline.br.exec(src); + if (cap) { + return { + type: "br", + raw: cap[0] + }; + } + } + del(src) { + const cap = this.rules.inline.del.exec(src); + if (cap) { + return { + type: "del", + raw: cap[0], + text: cap[2], + tokens: this.lexer.inlineTokens(cap[2]) + }; + } + } + autolink(src) { + const cap = this.rules.inline.autolink.exec(src); + if (cap) { + let text, href; + if (cap[2] === "@") { + text = cap[1]; + href = "mailto:" + text; + } else { + text = cap[1]; + href = text; + } + return { + type: "link", + raw: cap[0], + text, + href, + tokens: [ + { + type: "text", + raw: text, + text + } + ] + }; + } + } + url(src) { + let cap; + if (cap = this.rules.inline.url.exec(src)) { + let text, href; + if (cap[2] === "@") { + text = cap[0]; + href = "mailto:" + text; + } else { + let prevCapZero; + do { + prevCapZero = cap[0]; + cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? ""; + } while (prevCapZero !== cap[0]); + text = cap[0]; + if (cap[1] === "www.") { + href = "http://" + cap[0]; + } else { + href = cap[0]; + } + } + return { + type: "link", + raw: cap[0], + text, + href, + tokens: [ + { + type: "text", + raw: text, + text + } + ] + }; + } + } + inlineText(src) { + const cap = this.rules.inline.text.exec(src); + if (cap) { + const escaped = this.lexer.state.inRawBlock; + return { + type: "text", + raw: cap[0], + text: cap[0], + escaped + }; + } + } +}; +var _Lexer = class __Lexer { + tokens; + options; + state; + tokenizer; + inlineQueue; + constructor(options2) { + this.tokens = []; + this.tokens.links = /* @__PURE__ */ Object.create(null); + this.options = options2 || _defaults; + this.options.tokenizer = this.options.tokenizer || new _Tokenizer(); + this.tokenizer = this.options.tokenizer; + this.tokenizer.options = this.options; + this.tokenizer.lexer = this; + this.inlineQueue = []; + this.state = { + inLink: false, + inRawBlock: false, + top: true + }; + const rules = { + other, + block: block.normal, + inline: inline.normal + }; + if (this.options.pedantic) { + rules.block = block.pedantic; + rules.inline = inline.pedantic; + } else if (this.options.gfm) { + rules.block = block.gfm; + if (this.options.breaks) { + rules.inline = inline.breaks; + } else { + rules.inline = inline.gfm; + } + } + this.tokenizer.rules = rules; + } + /** + * Expose Rules + */ + static get rules() { + return { + block, + inline + }; + } + /** + * Static Lex Method + */ + static lex(src, options2) { + const lexer2 = new __Lexer(options2); + return lexer2.lex(src); + } + /** + * Static Lex Inline Method + */ + static lexInline(src, options2) { + const lexer2 = new __Lexer(options2); + return lexer2.inlineTokens(src); + } + /** + * Preprocessing + */ + lex(src) { + src = src.replace(other.carriageReturn, "\n"); + this.blockTokens(src, this.tokens); + for (let i = 0; i < this.inlineQueue.length; i++) { + const next = this.inlineQueue[i]; + this.inlineTokens(next.src, next.tokens); + } + this.inlineQueue = []; + return this.tokens; + } + blockTokens(src, tokens = [], lastParagraphClipped = false) { + if (this.options.pedantic) { + src = src.replace(other.tabCharGlobal, " ").replace(other.spaceLine, ""); + } + while (src) { + let token; + if (this.options.extensions?.block?.some((extTokenizer) => { + if (token = extTokenizer.call({ lexer: this }, src, tokens)) { + src = src.substring(token.raw.length); + tokens.push(token); + return true; + } + return false; + })) { + continue; + } + if (token = this.tokenizer.space(src)) { + src = src.substring(token.raw.length); + const lastToken = tokens.at(-1); + if (token.raw.length === 1 && lastToken !== void 0) { + lastToken.raw += "\n"; + } else { + tokens.push(token); + } + continue; + } + if (token = this.tokenizer.code(src)) { + src = src.substring(token.raw.length); + const lastToken = tokens.at(-1); + if (lastToken?.type === "paragraph" || lastToken?.type === "text") { + lastToken.raw += "\n" + token.raw; + lastToken.text += "\n" + token.text; + this.inlineQueue.at(-1).src = lastToken.text; + } else { + tokens.push(token); + } + continue; + } + if (token = this.tokenizer.fences(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.heading(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.hr(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.blockquote(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.list(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.html(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.def(src)) { + src = src.substring(token.raw.length); + const lastToken = tokens.at(-1); + if (lastToken?.type === "paragraph" || lastToken?.type === "text") { + lastToken.raw += "\n" + token.raw; + lastToken.text += "\n" + token.raw; + this.inlineQueue.at(-1).src = lastToken.text; + } else if (!this.tokens.links[token.tag]) { + this.tokens.links[token.tag] = { + href: token.href, + title: token.title + }; + } + continue; + } + if (token = this.tokenizer.table(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.lheading(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + let cutSrc = src; + if (this.options.extensions?.startBlock) { + let startIndex = Infinity; + const tempSrc = src.slice(1); + let tempStart; + this.options.extensions.startBlock.forEach((getStartIndex) => { + tempStart = getStartIndex.call({ lexer: this }, tempSrc); + if (typeof tempStart === "number" && tempStart >= 0) { + startIndex = Math.min(startIndex, tempStart); + } + }); + if (startIndex < Infinity && startIndex >= 0) { + cutSrc = src.substring(0, startIndex + 1); + } + } + if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) { + const lastToken = tokens.at(-1); + if (lastParagraphClipped && lastToken?.type === "paragraph") { + lastToken.raw += "\n" + token.raw; + lastToken.text += "\n" + token.text; + this.inlineQueue.pop(); + this.inlineQueue.at(-1).src = lastToken.text; + } else { + tokens.push(token); + } + lastParagraphClipped = cutSrc.length !== src.length; + src = src.substring(token.raw.length); + continue; + } + if (token = this.tokenizer.text(src)) { + src = src.substring(token.raw.length); + const lastToken = tokens.at(-1); + if (lastToken?.type === "text") { + lastToken.raw += "\n" + token.raw; + lastToken.text += "\n" + token.text; + this.inlineQueue.pop(); + this.inlineQueue.at(-1).src = lastToken.text; + } else { + tokens.push(token); + } + continue; + } + if (src) { + const errMsg = "Infinite loop on byte: " + src.charCodeAt(0); + if (this.options.silent) { + console.error(errMsg); + break; + } else { + throw new Error(errMsg); + } + } + } + this.state.top = true; + return tokens; + } + inline(src, tokens = []) { + this.inlineQueue.push({ src, tokens }); + return tokens; + } + /** + * Lexing/Compiling + */ + inlineTokens(src, tokens = []) { + let maskedSrc = src; + let match = null; + if (this.tokens.links) { + const links = Object.keys(this.tokens.links); + if (links.length > 0) { + while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) { + if (links.includes(match[0].slice(match[0].lastIndexOf("[") + 1, -1))) { + maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex); + } + } + } + } + while ((match = this.tokenizer.rules.inline.anyPunctuation.exec(maskedSrc)) != null) { + maskedSrc = maskedSrc.slice(0, match.index) + "++" + maskedSrc.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex); + } + while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) { + maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex); + } + let keepPrevChar = false; + let prevChar = ""; + while (src) { + if (!keepPrevChar) { + prevChar = ""; + } + keepPrevChar = false; + let token; + if (this.options.extensions?.inline?.some((extTokenizer) => { + if (token = extTokenizer.call({ lexer: this }, src, tokens)) { + src = src.substring(token.raw.length); + tokens.push(token); + return true; + } + return false; + })) { + continue; + } + if (token = this.tokenizer.escape(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.tag(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.link(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.reflink(src, this.tokens.links)) { + src = src.substring(token.raw.length); + const lastToken = tokens.at(-1); + if (token.type === "text" && lastToken?.type === "text") { + lastToken.raw += token.raw; + lastToken.text += token.text; + } else { + tokens.push(token); + } + continue; + } + if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.codespan(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.br(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.del(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (token = this.tokenizer.autolink(src)) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + if (!this.state.inLink && (token = this.tokenizer.url(src))) { + src = src.substring(token.raw.length); + tokens.push(token); + continue; + } + let cutSrc = src; + if (this.options.extensions?.startInline) { + let startIndex = Infinity; + const tempSrc = src.slice(1); + let tempStart; + this.options.extensions.startInline.forEach((getStartIndex) => { + tempStart = getStartIndex.call({ lexer: this }, tempSrc); + if (typeof tempStart === "number" && tempStart >= 0) { + startIndex = Math.min(startIndex, tempStart); + } + }); + if (startIndex < Infinity && startIndex >= 0) { + cutSrc = src.substring(0, startIndex + 1); + } + } + if (token = this.tokenizer.inlineText(cutSrc)) { + src = src.substring(token.raw.length); + if (token.raw.slice(-1) !== "_") { + prevChar = token.raw.slice(-1); + } + keepPrevChar = true; + const lastToken = tokens.at(-1); + if (lastToken?.type === "text") { + lastToken.raw += token.raw; + lastToken.text += token.text; + } else { + tokens.push(token); + } + continue; + } + if (src) { + const errMsg = "Infinite loop on byte: " + src.charCodeAt(0); + if (this.options.silent) { + console.error(errMsg); + break; + } else { + throw new Error(errMsg); + } + } + } + return tokens; + } +}; +var _Renderer = class { + options; + parser; + // set by the parser + constructor(options2) { + this.options = options2 || _defaults; + } + space(token) { + return ""; + } + code({ text, lang, escaped }) { + const langString = (lang || "").match(other.notSpaceStart)?.[0]; + const code = text.replace(other.endingNewline, "") + "\n"; + if (!langString) { + return "

" + (escaped ? code : escape2(code, true)) + "
\n"; + } + return '
' + (escaped ? code : escape2(code, true)) + "
\n"; + } + blockquote({ tokens }) { + const body = this.parser.parse(tokens); + return `
+${body}
+`; + } + html({ text }) { + return text; + } + heading({ tokens, depth }) { + return `${this.parser.parseInline(tokens)} +`; + } + hr(token) { + return "
\n"; + } + list(token) { + const ordered = token.ordered; + const start = token.start; + let body = ""; + for (let j = 0; j < token.items.length; j++) { + const item = token.items[j]; + body += this.listitem(item); + } + const type = ordered ? "ol" : "ul"; + const startAttr = ordered && start !== 1 ? ' start="' + start + '"' : ""; + return "<" + type + startAttr + ">\n" + body + "\n"; + } + listitem(item) { + let itemBody = ""; + if (item.task) { + const checkbox = this.checkbox({ checked: !!item.checked }); + if (item.loose) { + if (item.tokens[0]?.type === "paragraph") { + item.tokens[0].text = checkbox + " " + item.tokens[0].text; + if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === "text") { + item.tokens[0].tokens[0].text = checkbox + " " + escape2(item.tokens[0].tokens[0].text); + item.tokens[0].tokens[0].escaped = true; + } + } else { + item.tokens.unshift({ + type: "text", + raw: checkbox + " ", + text: checkbox + " ", + escaped: true + }); + } + } else { + itemBody += checkbox + " "; + } + } + itemBody += this.parser.parse(item.tokens, !!item.loose); + return `
  • ${itemBody}
  • +`; + } + checkbox({ checked }) { + return "'; + } + paragraph({ tokens }) { + return `

    ${this.parser.parseInline(tokens)}

    +`; + } + table(token) { + let header = ""; + let cell = ""; + for (let j = 0; j < token.header.length; j++) { + cell += this.tablecell(token.header[j]); + } + header += this.tablerow({ text: cell }); + let body = ""; + for (let j = 0; j < token.rows.length; j++) { + const row = token.rows[j]; + cell = ""; + for (let k = 0; k < row.length; k++) { + cell += this.tablecell(row[k]); + } + body += this.tablerow({ text: cell }); + } + if (body) body = `${body}`; + return "\n\n" + header + "\n" + body + "
    \n"; + } + tablerow({ text }) { + return ` +${text} +`; + } + tablecell(token) { + const content = this.parser.parseInline(token.tokens); + const type = token.header ? "th" : "td"; + const tag2 = token.align ? `<${type} align="${token.align}">` : `<${type}>`; + return tag2 + content + ` +`; + } + /** + * span level renderer + */ + strong({ tokens }) { + return `${this.parser.parseInline(tokens)}`; + } + em({ tokens }) { + return `${this.parser.parseInline(tokens)}`; + } + codespan({ text }) { + return `${escape2(text, true)}`; + } + br(token) { + return "
    "; + } + del({ tokens }) { + return `${this.parser.parseInline(tokens)}`; + } + link({ href, title, tokens }) { + const text = this.parser.parseInline(tokens); + const cleanHref = cleanUrl(href); + if (cleanHref === null) { + return text; + } + href = cleanHref; + let out = '
    "; + return out; + } + image({ href, title, text, tokens }) { + if (tokens) { + text = this.parser.parseInline(tokens, this.parser.textRenderer); + } + const cleanHref = cleanUrl(href); + if (cleanHref === null) { + return escape2(text); + } + href = cleanHref; + let out = `${text} { + const tokens2 = genericToken[childTokens].flat(Infinity); + values = values.concat(this.walkTokens(tokens2, callback)); + }); + } else if (genericToken.tokens) { + values = values.concat(this.walkTokens(genericToken.tokens, callback)); + } + } + } + } + return values; + } + use(...args) { + const extensions = this.defaults.extensions || { renderers: {}, childTokens: {} }; + args.forEach((pack) => { + const opts = { ...pack }; + opts.async = this.defaults.async || opts.async || false; + if (pack.extensions) { + pack.extensions.forEach((ext) => { + if (!ext.name) { + throw new Error("extension name required"); + } + if ("renderer" in ext) { + const prevRenderer = extensions.renderers[ext.name]; + if (prevRenderer) { + extensions.renderers[ext.name] = function(...args2) { + let ret = ext.renderer.apply(this, args2); + if (ret === false) { + ret = prevRenderer.apply(this, args2); + } + return ret; + }; + } else { + extensions.renderers[ext.name] = ext.renderer; + } + } + if ("tokenizer" in ext) { + if (!ext.level || ext.level !== "block" && ext.level !== "inline") { + throw new Error("extension level must be 'block' or 'inline'"); + } + const extLevel = extensions[ext.level]; + if (extLevel) { + extLevel.unshift(ext.tokenizer); + } else { + extensions[ext.level] = [ext.tokenizer]; + } + if (ext.start) { + if (ext.level === "block") { + if (extensions.startBlock) { + extensions.startBlock.push(ext.start); + } else { + extensions.startBlock = [ext.start]; + } + } else if (ext.level === "inline") { + if (extensions.startInline) { + extensions.startInline.push(ext.start); + } else { + extensions.startInline = [ext.start]; + } + } + } + } + if ("childTokens" in ext && ext.childTokens) { + extensions.childTokens[ext.name] = ext.childTokens; + } + }); + opts.extensions = extensions; + } + if (pack.renderer) { + const renderer = this.defaults.renderer || new _Renderer(this.defaults); + for (const prop in pack.renderer) { + if (!(prop in renderer)) { + throw new Error(`renderer '${prop}' does not exist`); + } + if (["options", "parser"].includes(prop)) { + continue; + } + const rendererProp = prop; + const rendererFunc = pack.renderer[rendererProp]; + const prevRenderer = renderer[rendererProp]; + renderer[rendererProp] = (...args2) => { + let ret = rendererFunc.apply(renderer, args2); + if (ret === false) { + ret = prevRenderer.apply(renderer, args2); + } + return ret || ""; + }; + } + opts.renderer = renderer; + } + if (pack.tokenizer) { + const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults); + for (const prop in pack.tokenizer) { + if (!(prop in tokenizer)) { + throw new Error(`tokenizer '${prop}' does not exist`); + } + if (["options", "rules", "lexer"].includes(prop)) { + continue; + } + const tokenizerProp = prop; + const tokenizerFunc = pack.tokenizer[tokenizerProp]; + const prevTokenizer = tokenizer[tokenizerProp]; + tokenizer[tokenizerProp] = (...args2) => { + let ret = tokenizerFunc.apply(tokenizer, args2); + if (ret === false) { + ret = prevTokenizer.apply(tokenizer, args2); + } + return ret; + }; + } + opts.tokenizer = tokenizer; + } + if (pack.hooks) { + const hooks = this.defaults.hooks || new _Hooks(); + for (const prop in pack.hooks) { + if (!(prop in hooks)) { + throw new Error(`hook '${prop}' does not exist`); + } + if (["options", "block"].includes(prop)) { + continue; + } + const hooksProp = prop; + const hooksFunc = pack.hooks[hooksProp]; + const prevHook = hooks[hooksProp]; + if (_Hooks.passThroughHooks.has(prop)) { + hooks[hooksProp] = (arg) => { + if (this.defaults.async) { + return Promise.resolve(hooksFunc.call(hooks, arg)).then((ret2) => { + return prevHook.call(hooks, ret2); + }); + } + const ret = hooksFunc.call(hooks, arg); + return prevHook.call(hooks, ret); + }; + } else { + hooks[hooksProp] = (...args2) => { + let ret = hooksFunc.apply(hooks, args2); + if (ret === false) { + ret = prevHook.apply(hooks, args2); + } + return ret; + }; + } + } + opts.hooks = hooks; + } + if (pack.walkTokens) { + const walkTokens2 = this.defaults.walkTokens; + const packWalktokens = pack.walkTokens; + opts.walkTokens = function(token) { + let values = []; + values.push(packWalktokens.call(this, token)); + if (walkTokens2) { + values = values.concat(walkTokens2.call(this, token)); + } + return values; + }; + } + this.defaults = { ...this.defaults, ...opts }; + }); + return this; + } + setOptions(opt) { + this.defaults = { ...this.defaults, ...opt }; + return this; + } + lexer(src, options2) { + return _Lexer.lex(src, options2 ?? this.defaults); + } + parser(tokens, options2) { + return _Parser.parse(tokens, options2 ?? this.defaults); + } + parseMarkdown(blockType) { + const parse2 = (src, options2) => { + const origOpt = { ...options2 }; + const opt = { ...this.defaults, ...origOpt }; + const throwError = this.onError(!!opt.silent, !!opt.async); + if (this.defaults.async === true && origOpt.async === false) { + return throwError(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise.")); + } + if (typeof src === "undefined" || src === null) { + return throwError(new Error("marked(): input parameter is undefined or null")); + } + if (typeof src !== "string") { + return throwError(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(src) + ", string expected")); + } + if (opt.hooks) { + opt.hooks.options = opt; + opt.hooks.block = blockType; + } + const lexer2 = opt.hooks ? opt.hooks.provideLexer() : blockType ? _Lexer.lex : _Lexer.lexInline; + const parser2 = opt.hooks ? opt.hooks.provideParser() : blockType ? _Parser.parse : _Parser.parseInline; + if (opt.async) { + return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src).then((src2) => lexer2(src2, opt)).then((tokens) => opt.hooks ? opt.hooks.processAllTokens(tokens) : tokens).then((tokens) => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens).then((tokens) => parser2(tokens, opt)).then((html2) => opt.hooks ? opt.hooks.postprocess(html2) : html2).catch(throwError); + } + try { + if (opt.hooks) { + src = opt.hooks.preprocess(src); + } + let tokens = lexer2(src, opt); + if (opt.hooks) { + tokens = opt.hooks.processAllTokens(tokens); + } + if (opt.walkTokens) { + this.walkTokens(tokens, opt.walkTokens); + } + let html2 = parser2(tokens, opt); + if (opt.hooks) { + html2 = opt.hooks.postprocess(html2); + } + return html2; + } catch (e) { + return throwError(e); + } + }; + return parse2; + } + onError(silent, async) { + return (e) => { + e.message += "\nPlease report this to https://github.com/markedjs/marked."; + if (silent) { + const msg = "

    An error occurred:

    " + escape2(e.message + "", true) + "
    "; + if (async) { + return Promise.resolve(msg); + } + return msg; + } + if (async) { + return Promise.reject(e); + } + throw e; + }; + } +}; +var markedInstance = new Marked(); +function marked(src, opt) { + return markedInstance.parse(src, opt); +} +marked.options = marked.setOptions = function(options2) { + markedInstance.setOptions(options2); + marked.defaults = markedInstance.defaults; + changeDefaults(marked.defaults); + return marked; +}; +marked.getDefaults = _getDefaults; +marked.defaults = _defaults; +marked.use = function(...args) { + markedInstance.use(...args); + marked.defaults = markedInstance.defaults; + changeDefaults(marked.defaults); + return marked; +}; +marked.walkTokens = function(tokens, callback) { + return markedInstance.walkTokens(tokens, callback); +}; +marked.parseInline = markedInstance.parseInline; +marked.Parser = _Parser; +marked.parser = _Parser.parse; +marked.Renderer = _Renderer; +marked.TextRenderer = _TextRenderer; +marked.Lexer = _Lexer; +marked.lexer = _Lexer.lex; +marked.Tokenizer = _Tokenizer; +marked.Hooks = _Hooks; +marked.parse = marked; +marked.options; +marked.setOptions; +marked.use; +marked.walkTokens; +marked.parseInline; +_Parser.parse; +_Lexer.lex; + +// src2/markdown/ApplyBlockHoverButtons.tsx +var import_react2 = __toESM(require_react(), 1); + +// src2/util/helpers.tsx +var import_react = __toESM(require_react(), 1); +var useRefState = (initVal) => { + const [_s, _setState] = (0, import_react.useState)(0); + const ref = (0, import_react.useRef)(initVal); + const setState = (0, import_react.useCallback)((newVal) => { + _setState((n) => n + 1); + ref.current = newVal; + }, []); + return [ref, setState]; +}; +var import_jsx_runtime = __toESM(require_jsx_runtime(), 1); +var IconShell1 = ({ onClick, Icon: Icon2, disabled, className, ...props }) => { + return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + "button", + { + disabled, + onClick: (e) => { + e.preventDefault(); + e.stopPropagation(); + onClick?.(e); + }, + className: ` void-size-[18px] void-p-[2px] void-flex void-items-center void-justify-center void-text-sm void-text-void-fg-3 hover:void-brightness-110 disabled:void-opacity-50 disabled:void-cursor-not-allowed ${className} `, + ...props, + children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon2, {}) + } + ); +}; +var COPY_FEEDBACK_TIMEOUT = 1500; +var CopyButton = ({ codeStr, toolTipName }) => { + const accessor = useAccessor(); + const metricsService = accessor.get("IMetricsService"); + const clipboardService = accessor.get("IClipboardService"); + const [copyButtonText, setCopyButtonText] = (0, import_react2.useState)("Copy" /* Idle */); + (0, import_react2.useEffect)(() => { + if (copyButtonText === "Copy" /* Idle */) return; + setTimeout(() => { + setCopyButtonText("Copy" /* Idle */); + }, COPY_FEEDBACK_TIMEOUT); + }, [copyButtonText]); + const onCopy = (0, import_react2.useCallback)(async () => { + clipboardService.writeText(typeof codeStr === "string" ? codeStr : await codeStr()).then(() => { + setCopyButtonText("Copied!" /* Copied */); + }).catch(() => { + setCopyButtonText("Could not copy" /* Error */); + }); + metricsService.capture("Copy Code", { length: codeStr.length }); + }, [metricsService, clipboardService, codeStr, setCopyButtonText]); + return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + IconShell1, + { + Icon: copyButtonText === "Copied!" /* Copied */ ? Check : copyButtonText === "Could not copy" /* Error */ ? X : Copy, + onClick: onCopy, + ...tooltipPropsForApplyBlock({ tooltipName: toolTipName }) + } + ); +}; +var JumpToFileButton = ({ uri, ...props }) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const jumpToFileButton = uri !== "current" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + IconShell1, + { + Icon: FileSymlink, + onClick: () => { + voidOpenFileFn(uri, accessor); + }, + ...tooltipPropsForApplyBlock({ tooltipName: "Go to file" }), + ...props + } + ); + return jumpToFileButton; +}; +var _applyingURIOfApplyBoxIdRef = { current: {} }; +var getUriBeingApplied = (applyBoxId) => { + return _applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null; +}; +var useApplyStreamState = ({ applyBoxId }) => { + const accessor = useAccessor(); + const cortexideCommandBarService = accessor.get("ICortexideCommandBarService"); + const getStreamState = (0, import_react2.useCallback)(() => { + const uri = getUriBeingApplied(applyBoxId); + if (!uri) return "idle-no-changes"; + return cortexideCommandBarService.getStreamState(uri); + }, [cortexideCommandBarService, applyBoxId]); + const [currStreamStateRef, setStreamState] = useRefState(getStreamState()); + const setApplying = (0, import_react2.useCallback)((uri) => { + _applyingURIOfApplyBoxIdRef.current[applyBoxId] = uri ?? void 0; + setStreamState(getStreamState()); + }, [setStreamState, getStreamState, applyBoxId]); + useCommandBarURIListener((0, import_react2.useCallback)((uri_) => { + const uri = getUriBeingApplied(applyBoxId); + if (uri?.fsPath === uri_.fsPath) { + setStreamState(getStreamState()); + } + }, [setStreamState, applyBoxId, getStreamState])); + return { currStreamStateRef, setApplying }; +}; +var StatusIndicator = ({ indicatorColor, title, className, ...props }) => { + return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `void-flex void-flex-row void-text-void-fg-3 void-text-xs void-items-center void-gap-1.5 ${className}`, ...props, children: [ + title && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "void-opacity-80", children: title }), + /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + "div", + { + className: ` void-size-1.5 void-rounded-full void-border ${indicatorColor === "dark" ? "void-bg-[rgba(0,0,0,0)] void-border-void-border-1" : indicatorColor === "orange" ? "void-bg-orange-500 void-border-orange-500 void-shadow-[0_0_4px_0px_rgba(234,88,12,0.6)]" : indicatorColor === "green" ? "void-bg-green-500 void-border-green-500 void-shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]" : indicatorColor === "yellow" ? "void-bg-yellow-500 void-border-yellow-500 void-shadow-[0_0_4px_0px_rgba(22,163,74,0.6)]" : "void-bg-void-border-1 void-border-void-border-1"} ` + } + ) + ] }); +}; +var tooltipPropsForApplyBlock = ({ tooltipName, color = void 0, position = "top", offset: offset3 = void 0 }) => ({ + "data-tooltip-id": color === "orange" ? `void-tooltip-orange` : color === "green" ? "void-tooltip-green" : "void-tooltip", + "data-tooltip-place": position, + "data-tooltip-content": `${tooltipName}`, + "data-tooltip-offset": offset3 +}); +var useEditToolStreamState = ({ applyBoxId, uri }) => { + const accessor = useAccessor(); + const cortexideCommandBarService = accessor.get("ICortexideCommandBarService"); + const [streamState, setStreamState] = (0, import_react2.useState)(cortexideCommandBarService.getStreamState(uri)); + useCommandBarURIListener((0, import_react2.useCallback)((uri_) => { + const shouldUpdate = uri.fsPath === uri_.fsPath; + if (shouldUpdate) { + setStreamState(cortexideCommandBarService.getStreamState(uri)); + } + }, [cortexideCommandBarService, applyBoxId, uri])); + return { streamState }; +}; +var StatusIndicatorForApplyButton = ({ applyBoxId, uri }) => { + const { currStreamStateRef } = useApplyStreamState({ applyBoxId }); + const currStreamState = currStreamStateRef.current; + const color = currStreamState === "idle-no-changes" ? "dark" : currStreamState === "streaming" ? "orange" : currStreamState === "idle-has-changes" ? "green" : null; + const tooltipName = currStreamState === "idle-no-changes" ? "Done" : currStreamState === "streaming" ? "Applying" : currStreamState === "idle-has-changes" ? "Done" : ( + // also 'Done'? 'Applied' looked bad + "" + ); + const statusIndicatorHTML = /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + StatusIndicator, + { + className: "void-mx-2", + indicatorColor: color, + ...tooltipPropsForApplyBlock({ tooltipName, color, position: "top", offset: 12 }) + }, + currStreamState + ); + return statusIndicatorHTML; +}; +var terminalLanguages = /* @__PURE__ */ new Set( + [ + "bash", + "shellscript", + "shell", + "powershell", + "bat", + "zsh", + "sh", + "fish", + "nushell", + "ksh", + "xonsh", + "elvish" + ] +); +var ApplyButtonsForTerminal = ({ + codeStr, + applyBoxId, + uri, + language +}) => { + const accessor = useAccessor(); + const metricsService = accessor.get("IMetricsService"); + const terminalToolService = accessor.get("ITerminalToolService"); + useSettingsState(); + const [isShellRunning, setIsShellRunning] = (0, import_react2.useState)(false); + const interruptToolRef = (0, import_react2.useRef)(null); + const isDisabled = isShellRunning; + const onClickSubmit = (0, import_react2.useCallback)(async () => { + if (isShellRunning) return; + try { + setIsShellRunning(true); + const terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }); + const { interrupt } = await terminalToolService.runCommand( + codeStr, + { type: "persistent", persistentTerminalId: terminalId } + ); + interruptToolRef.current = interrupt; + metricsService.capture("Execute Shell", { length: codeStr.length }); + } catch (e) { + setIsShellRunning(false); + console.error("Failed to execute in terminal:", e); + } + }, [codeStr, uri, applyBoxId, metricsService, terminalToolService, isShellRunning]); + if (isShellRunning) { + return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + IconShell1, + { + Icon: X, + onClick: () => { + interruptToolRef.current?.(); + setIsShellRunning(false); + }, + ...tooltipPropsForApplyBlock({ tooltipName: "Stop" }) + } + ); + } + if (isDisabled) { + return null; + } + return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + IconShell1, + { + Icon: Play, + onClick: onClickSubmit, + ...tooltipPropsForApplyBlock({ tooltipName: "Apply" }) + } + ); +}; +var ApplyButtonsForEdit = ({ + codeStr, + applyBoxId, + uri, + language +}) => { + const accessor = useAccessor(); + const editCodeService = accessor.get("IEditCodeService"); + const metricsService = accessor.get("IMetricsService"); + const notificationService2 = accessor.get("INotificationService"); + const settingsState = useSettingsState(); + const isDisabled = !!isFeatureNameDisabled("Apply", settingsState) || !applyBoxId; + const { currStreamStateRef, setApplying } = useApplyStreamState({ applyBoxId }); + const onClickSubmit = (0, import_react2.useCallback)(async () => { + if (currStreamStateRef.current === "streaming") return; + await editCodeService.callBeforeApplyOrEdit(uri); + const [newApplyingUri, applyDonePromise] = editCodeService.startApplying({ + from: "ClickApply", + applyStr: codeStr, + uri, + startBehavior: "reject-conflicts" + }) ?? []; + setApplying(newApplyingUri); + if (!applyDonePromise) { + notificationService2.info(`CortexIDE Error: We couldn't run Apply here. ${uri === "current" ? "This Apply block wants to run on the current file, but you might not have a file open." : `This Apply block wants to run on ${uri.fsPath}, but it might not exist.`}`); + } + applyDonePromise?.catch((e) => { + const uri2 = getUriBeingApplied(applyBoxId); + if (uri2) editCodeService.interruptURIStreaming({ uri: uri2 }); + notificationService2.info(`CortexIDE Error: There was a problem running Apply: ${e}.`); + }); + metricsService.capture("Apply Code", { length: codeStr.length }); + }, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService, notificationService2]); + const onClickStop = (0, import_react2.useCallback)(() => { + if (currStreamStateRef.current !== "streaming") return; + const uri2 = getUriBeingApplied(applyBoxId); + if (!uri2) return; + editCodeService.interruptURIStreaming({ uri: uri2 }); + metricsService.capture("Stop Apply", {}); + }, [currStreamStateRef, applyBoxId, editCodeService, metricsService]); + const onAccept = (0, import_react2.useCallback)(() => { + const uri2 = getUriBeingApplied(applyBoxId); + if (uri2) editCodeService.acceptOrRejectAllDiffAreas({ uri: uri2, behavior: "accept", removeCtrlKs: false }); + }, [uri, applyBoxId, editCodeService]); + const onReject = (0, import_react2.useCallback)(() => { + const uri2 = getUriBeingApplied(applyBoxId); + if (uri2) editCodeService.acceptOrRejectAllDiffAreas({ uri: uri2, behavior: "reject", removeCtrlKs: false }); + }, [uri, applyBoxId, editCodeService]); + const currStreamState = currStreamStateRef.current; + if (currStreamState === "streaming") { + return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + IconShell1, + { + Icon: Square, + onClick: onClickStop, + ...tooltipPropsForApplyBlock({ tooltipName: "Stop" }) + } + ); + } + if (isDisabled) { + return null; + } + if (currStreamState === "idle-no-changes") { + return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + IconShell1, + { + Icon: Play, + onClick: onClickSubmit, + ...tooltipPropsForApplyBlock({ tooltipName: "Apply" }) + } + ); + } + if (currStreamState === "idle-has-changes") { + return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react2.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + IconShell1, + { + Icon: X, + onClick: onReject, + ...tooltipPropsForApplyBlock({ tooltipName: "Remove" }) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + IconShell1, + { + Icon: Check, + onClick: onAccept, + ...tooltipPropsForApplyBlock({ tooltipName: "Keep" }) + } + ) + ] }); + } +}; +var ApplyButtonsHTML = (params) => { + const { language } = params; + const isShellLanguage = !!language && terminalLanguages.has(language); + if (isShellLanguage) { + return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApplyButtonsForTerminal, { ...params }); + } else { + return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApplyButtonsForEdit, { ...params }); + } +}; +var EditToolAcceptRejectButtonsHTML = ({ + codeStr, + applyBoxId, + uri, + type, + threadId +}) => { + const accessor = useAccessor(); + const editCodeService = accessor.get("IEditCodeService"); + accessor.get("IMetricsService"); + const { streamState } = useEditToolStreamState({ applyBoxId, uri }); + const settingsState = useSettingsState(); + const chatThreadsStreamState = useChatThreadsStreamState(threadId); + const isRunning = chatThreadsStreamState?.isRunning; + const isDisabled = !!isFeatureNameDisabled("Chat", settingsState) || !applyBoxId; + const onAccept = (0, import_react2.useCallback)(() => { + editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: "accept", removeCtrlKs: false }); + }, [uri, applyBoxId, editCodeService]); + const onReject = (0, import_react2.useCallback)(() => { + editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: "reject", removeCtrlKs: false }); + }, [uri, applyBoxId, editCodeService]); + if (isDisabled) return null; + if (streamState === "idle-no-changes") { + return null; + } + if (streamState === "idle-has-changes") { + if (isRunning === "LLM" || isRunning === "tool") return null; + return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + IconShell1, + { + Icon: X, + onClick: onReject, + ...tooltipPropsForApplyBlock({ tooltipName: "Remove" }) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + IconShell1, + { + Icon: Check, + onClick: onAccept, + ...tooltipPropsForApplyBlock({ tooltipName: "Keep" }) + } + ) + ] }); + } +}; +var BlockCodeApplyWrapper = ({ + children, + codeStr, + applyBoxId, + language, + canApply, + uri +}) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const { currStreamStateRef } = useApplyStreamState({ applyBoxId }); + const currStreamState = currStreamStateRef.current; + const name = uri !== "current" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + ListableToolItem, + { + name: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "void-not-italic", children: getBasename(uri.fsPath) }), + isSmall: true, + showDot: false, + onClick: () => { + voidOpenFileFn(uri, accessor); + } + } + ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: language }); + return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "void-border void-border-void-border-3 void-rounded void-overflow-hidden void-bg-void-bg-3 void-my-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: " void-select-none void-flex void-justify-between void-items-center void-py-1 void-px-2 void-border-b void-border-void-border-3 void-cursor-default", children: [ + /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "void-flex void-items-center", children: [ + /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusIndicatorForApplyButton, { uri, applyBoxId }), + /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "void-text-[13px] void-font-light void-text-void-fg-3", children: name }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `${canApply ? "" : "void-hidden"} void-flex void-items-center void-gap-1`, children: [ + /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JumpToFileButton, { uri }), + currStreamState === "idle-no-changes" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, { codeStr, toolTipName: "Copy" }), + /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApplyButtonsHTML, { uri, applyBoxId, codeStr, language }) + ] }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolChildrenWrapper, { children }) + ] }); +}; + +// src2/util/inputs.tsx +var import_react5 = __toESM(require_react(), 1); + +// ../../../../../../../node_modules/@floating-ui/react/dist/floating-ui.react.mjs +var React3 = __toESM(require_react(), 1); + +// ../../../../../../../node_modules/@floating-ui/react/dist/floating-ui.react.utils.mjs +var React = __toESM(require_react(), 1); +var import_react3 = __toESM(require_react(), 1); +var isClient = typeof document !== "undefined"; +var noop = function noop2() { +}; +var index = isClient ? import_react3.useLayoutEffect : noop; +var SafeReact = { + ...React +}; +var useInsertionEffect = SafeReact.useInsertionEffect; +var useSafeInsertionEffect = useInsertionEffect || ((fn) => fn()); +function useEffectEvent(callback) { + const ref = React.useRef(() => { + { + throw new Error("Cannot call an event handler while rendering."); + } + }); + useSafeInsertionEffect(() => { + ref.current = callback; + }); + return React.useCallback(function() { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + return ref.current == null ? void 0 : ref.current(...args); + }, []); +} + +// ../../../../../../../node_modules/@floating-ui/react/dist/floating-ui.react.mjs +__toESM(require_jsx_runtime(), 1); +__toESM(require_react_dom(), 1); + +// ../../../../../../../node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.mjs +var React2 = __toESM(require_react(), 1); +var import_react4 = __toESM(require_react(), 1); +var ReactDOM = __toESM(require_react_dom(), 1); +var isClient2 = typeof document !== "undefined"; +var noop3 = function noop4() { +}; +var index2 = isClient2 ? import_react4.useLayoutEffect : noop3; +function deepEqual(a, b) { + if (a === b) { + return true; + } + if (typeof a !== typeof b) { + return false; + } + if (typeof a === "function" && a.toString() === b.toString()) { + return true; + } + let length; + let i; + let keys; + if (a && b && typeof a === "object") { + if (Array.isArray(a)) { + length = a.length; + if (length !== b.length) return false; + for (i = length; i-- !== 0; ) { + if (!deepEqual(a[i], b[i])) { + return false; + } + } + return true; + } + keys = Object.keys(a); + length = keys.length; + if (length !== Object.keys(b).length) { + return false; + } + for (i = length; i-- !== 0; ) { + if (!{}.hasOwnProperty.call(b, keys[i])) { + return false; + } + } + for (i = length; i-- !== 0; ) { + const key = keys[i]; + if (key === "_owner" && a.$$typeof) { + continue; + } + if (!deepEqual(a[key], b[key])) { + return false; + } + } + return true; + } + return a !== a && b !== b; +} +function getDPR(element) { + if (typeof window === "undefined") { + return 1; + } + const win = element.ownerDocument.defaultView || window; + return win.devicePixelRatio || 1; +} +function roundByDPR(element, value) { + const dpr = getDPR(element); + return Math.round(value * dpr) / dpr; +} +function useLatestRef(value) { + const ref = React2.useRef(value); + index2(() => { + ref.current = value; + }); + return ref; +} +function useFloating(options2) { + if (options2 === void 0) { + options2 = {}; + } + const { + placement = "bottom", + strategy = "absolute", + middleware = [], + platform: platform2, + elements: { + reference: externalReference, + floating: externalFloating + } = {}, + transform = true, + whileElementsMounted, + open + } = options2; + const [data, setData] = React2.useState({ + x: 0, + y: 0, + strategy, + placement, + middlewareData: {}, + isPositioned: false + }); + const [latestMiddleware, setLatestMiddleware] = React2.useState(middleware); + if (!deepEqual(latestMiddleware, middleware)) { + setLatestMiddleware(middleware); + } + const [_reference, _setReference] = React2.useState(null); + const [_floating, _setFloating] = React2.useState(null); + const setReference = React2.useCallback((node) => { + if (node !== referenceRef.current) { + referenceRef.current = node; + _setReference(node); + } + }, []); + const setFloating = React2.useCallback((node) => { + if (node !== floatingRef.current) { + floatingRef.current = node; + _setFloating(node); + } + }, []); + const referenceEl = externalReference || _reference; + const floatingEl = externalFloating || _floating; + const referenceRef = React2.useRef(null); + const floatingRef = React2.useRef(null); + const dataRef = React2.useRef(data); + const hasWhileElementsMounted = whileElementsMounted != null; + const whileElementsMountedRef = useLatestRef(whileElementsMounted); + const platformRef = useLatestRef(platform2); + const openRef = useLatestRef(open); + const update = React2.useCallback(() => { + if (!referenceRef.current || !floatingRef.current) { + return; + } + const config = { + placement, + strategy, + middleware: latestMiddleware + }; + if (platformRef.current) { + config.platform = platformRef.current; + } + computePosition(referenceRef.current, floatingRef.current, config).then((data2) => { + const fullData = { + ...data2, + // The floating element's position may be recomputed while it's closed + // but still mounted (such as when transitioning out). To ensure + // `isPositioned` will be `false` initially on the next open, avoid + // setting it to `true` when `open === false` (must be specified). + isPositioned: openRef.current !== false + }; + if (isMountedRef.current && !deepEqual(dataRef.current, fullData)) { + dataRef.current = fullData; + ReactDOM.flushSync(() => { + setData(fullData); + }); + } + }); + }, [latestMiddleware, placement, strategy, platformRef, openRef]); + index2(() => { + if (open === false && dataRef.current.isPositioned) { + dataRef.current.isPositioned = false; + setData((data2) => ({ + ...data2, + isPositioned: false + })); + } + }, [open]); + const isMountedRef = React2.useRef(false); + index2(() => { + isMountedRef.current = true; + return () => { + isMountedRef.current = false; + }; + }, []); + index2(() => { + if (referenceEl) referenceRef.current = referenceEl; + if (floatingEl) floatingRef.current = floatingEl; + if (referenceEl && floatingEl) { + if (whileElementsMountedRef.current) { + return whileElementsMountedRef.current(referenceEl, floatingEl, update); + } + update(); + } + }, [referenceEl, floatingEl, update, whileElementsMountedRef, hasWhileElementsMounted]); + const refs = React2.useMemo(() => ({ + reference: referenceRef, + floating: floatingRef, + setReference, + setFloating + }), [setReference, setFloating]); + const elements = React2.useMemo(() => ({ + reference: referenceEl, + floating: floatingEl + }), [referenceEl, floatingEl]); + const floatingStyles = React2.useMemo(() => { + const initialStyles = { + position: strategy, + left: 0, + top: 0 + }; + if (!elements.floating) { + return initialStyles; + } + const x = roundByDPR(elements.floating, data.x); + const y = roundByDPR(elements.floating, data.y); + if (transform) { + return { + ...initialStyles, + transform: "translate(" + x + "px, " + y + "px)", + ...getDPR(elements.floating) >= 1.5 && { + willChange: "transform" + } + }; + } + return { + position: strategy, + left: x, + top: y + }; + }, [strategy, transform, elements.floating, data.x, data.y]); + return React2.useMemo(() => ({ + ...data, + update, + refs, + elements, + floatingStyles + }), [data, update, refs, elements, floatingStyles]); +} +var offset2 = (options2, deps) => ({ + ...offset(options2), + options: [options2, deps] +}); +var shift2 = (options2, deps) => ({ + ...shift(options2), + options: [options2, deps] +}); +var flip2 = (options2, deps) => ({ + ...flip(options2), + options: [options2, deps] +}); +var size2 = (options2, deps) => ({ + ...size(options2), + options: [options2, deps] +}); +var SafeReact2 = { + ...React3 +}; +var serverHandoffComplete = false; +var count = 0; +var genId = () => ( + // Ensure the id is unique with multiple independent versions of Floating UI + // on serverHandoffComplete ? genId() : void 0); + index(() => { + if (id == null) { + setId(genId()); + } + }, []); + React3.useEffect(() => { + serverHandoffComplete = true; + }, []); + return id; +} +var useReactId = SafeReact2.useId; +var useId = useReactId || useFloatingId; +var devMessageSet; +{ + devMessageSet = /* @__PURE__ */ new Set(); +} +function error() { + var _devMessageSet3; + for (var _len2 = arguments.length, messages = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + messages[_key2] = arguments[_key2]; + } + const message = "Floating UI: " + messages.join(" "); + if (!((_devMessageSet3 = devMessageSet) != null && _devMessageSet3.has(message))) { + var _devMessageSet4; + (_devMessageSet4 = devMessageSet) == null || _devMessageSet4.add(message); + console.error(message); + } +} +function createEventEmitter() { + const map = /* @__PURE__ */ new Map(); + return { + emit(event, data) { + var _map$get; + (_map$get = map.get(event)) == null || _map$get.forEach((listener) => listener(data)); + }, + on(event, listener) { + if (!map.has(event)) { + map.set(event, /* @__PURE__ */ new Set()); + } + map.get(event).add(listener); + }, + off(event, listener) { + var _map$get2; + (_map$get2 = map.get(event)) == null || _map$get2.delete(listener); + } + }; +} +var FloatingNodeContext = /* @__PURE__ */ React3.createContext(null); +var FloatingTreeContext = /* @__PURE__ */ React3.createContext(null); +var useFloatingParentNodeId = () => { + var _React$useContext; + return ((_React$useContext = React3.useContext(FloatingNodeContext)) == null ? void 0 : _React$useContext.id) || null; +}; +var useFloatingTree = () => React3.useContext(FloatingTreeContext); +function useFloatingRootContext(options2) { + const { + open = false, + onOpenChange: onOpenChangeProp, + elements: elementsProp + } = options2; + const floatingId = useId(); + const dataRef = React3.useRef({}); + const [events] = React3.useState(() => createEventEmitter()); + const nested = useFloatingParentNodeId() != null; + { + const optionDomReference = elementsProp.reference; + if (optionDomReference && !isElement(optionDomReference)) { + error("Cannot pass a virtual element to the `elements.reference` option,", "as it must be a real DOM element. Use `refs.setPositionReference()`", "instead."); + } + } + const [positionReference, setPositionReference] = React3.useState(elementsProp.reference); + const onOpenChange = useEffectEvent((open2, event, reason) => { + dataRef.current.openEvent = open2 ? event : void 0; + events.emit("openchange", { + open: open2, + event, + reason, + nested + }); + onOpenChangeProp == null || onOpenChangeProp(open2, event, reason); + }); + const refs = React3.useMemo(() => ({ + setPositionReference + }), []); + const elements = React3.useMemo(() => ({ + reference: positionReference || elementsProp.reference || null, + floating: elementsProp.floating || null, + domReference: elementsProp.reference + }), [positionReference, elementsProp.reference, elementsProp.floating]); + return React3.useMemo(() => ({ + dataRef, + open, + onOpenChange, + elements, + events, + floatingId, + refs + }), [open, onOpenChange, elements, events, floatingId, refs]); +} +function useFloating2(options2) { + if (options2 === void 0) { + options2 = {}; + } + const { + nodeId + } = options2; + const internalRootContext = useFloatingRootContext({ + ...options2, + elements: { + reference: null, + floating: null, + ...options2.elements + } + }); + const rootContext = options2.rootContext || internalRootContext; + const computedElements = rootContext.elements; + const [_domReference, setDomReference] = React3.useState(null); + const [positionReference, _setPositionReference] = React3.useState(null); + const optionDomReference = computedElements == null ? void 0 : computedElements.domReference; + const domReference = optionDomReference || _domReference; + const domReferenceRef = React3.useRef(null); + const tree = useFloatingTree(); + index(() => { + if (domReference) { + domReferenceRef.current = domReference; + } + }, [domReference]); + const position = useFloating({ + ...options2, + elements: { + ...computedElements, + ...positionReference && { + reference: positionReference + } + } + }); + const setPositionReference = React3.useCallback((node) => { + const computedPositionReference = isElement(node) ? { + getBoundingClientRect: () => node.getBoundingClientRect(), + getClientRects: () => node.getClientRects(), + contextElement: node + } : node; + _setPositionReference(computedPositionReference); + position.refs.setReference(computedPositionReference); + }, [position.refs]); + const setReference = React3.useCallback((node) => { + if (isElement(node) || node === null) { + domReferenceRef.current = node; + setDomReference(node); + } + if (isElement(position.refs.reference.current) || position.refs.reference.current === null || // Don't allow setting virtual elements using the old technique back to + // `null` to support `positionReference` + an unstable `reference` + // callback ref. + node !== null && !isElement(node)) { + position.refs.setReference(node); + } + }, [position.refs]); + const refs = React3.useMemo(() => ({ + ...position.refs, + setReference, + setPositionReference, + domReference: domReferenceRef + }), [position.refs, setReference, setPositionReference]); + const elements = React3.useMemo(() => ({ + ...position.elements, + domReference + }), [position.elements, domReference]); + const context = React3.useMemo(() => ({ + ...position, + ...rootContext, + refs, + elements, + nodeId + }), [position, refs, elements, nodeId, rootContext]); + index(() => { + rootContext.dataRef.current.floatingContext = context; + const node = tree == null ? void 0 : tree.nodesRef.current.find((node2) => node2.id === nodeId); + if (node) { + node.context = context; + } + }); + return React3.useMemo(() => ({ + ...position, + context, + refs, + elements + }), [position, refs, elements, context]); +} +var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1); +var isConstructor = (f) => { + return !!f.prototype && f.prototype.constructor === f; +}; +var WidgetComponent = ({ + ctor, + propsFn, + dispose, + onCreateInstance, + children, + className +}) => { + const containerRef = (0, import_react5.useRef)(null); + (0, import_react5.useEffect)(() => { + const instance = isConstructor(ctor) ? new ctor(...propsFn(containerRef.current)) : ctor(containerRef.current); + const disposables = onCreateInstance(instance); + return () => { + disposables.forEach((d) => d.dispose()); + dispose(instance); + }; + }, [ctor, propsFn, dispose, onCreateInstance, containerRef]); + return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: containerRef, className: className === void 0 ? `void-w-full` : className, children }); +}; +var isSubsequence = (text, pattern) => { + text = text.toLowerCase(); + pattern = pattern.toLowerCase(); + if (pattern === "") return true; + if (text === "") return false; + if (pattern.length > text.length) return false; + const seq = Array(pattern.length + 1).fill(null).map(() => Array(text.length + 1).fill(false)); + for (let j = 0; j <= text.length; j++) { + seq[0][j] = true; + } + for (let i = 1; i <= pattern.length; i++) { + for (let j = 1; j <= text.length; j++) { + if (pattern[i - 1] === text[j - 1]) { + seq[i][j] = seq[i - 1][j - 1]; + } else { + seq[i][j] = seq[i][j - 1]; + } + } + } + return seq[pattern.length][text.length]; +}; +var scoreSubsequence = (text, pattern) => { + if (pattern === "") return 0; + text = text.toLowerCase(); + pattern = pattern.toLowerCase(); + const n = text.length; + const m = pattern.length; + let maxConsecutive = 0; + for (let i = 0; i < n; i++) { + let consecutiveCount = 0; + for (let j = 0; j < m; j++) { + if (i + j < n && text[i + j] === pattern[j]) { + consecutiveCount++; + } else { + break; + } + } + maxConsecutive = Math.max(maxConsecutive, consecutiveCount); + } + return maxConsecutive; +}; +function getRelativeWorkspacePath(accessor, uri) { + const workspaceService = accessor.get("IWorkspaceContextService"); + const workspaceFolders = workspaceService.getWorkspace().folders; + if (!workspaceFolders.length) { + return uri.fsPath; + } + const sortedFolders = [...workspaceFolders].sort( + (a, b) => b.uri.fsPath.length - a.uri.fsPath.length + ); + const uriPath = uri.fsPath.endsWith("/") ? uri.fsPath : uri.fsPath + "/"; + for (const folder of sortedFolders) { + const folderPath = folder.uri.fsPath.endsWith("/") ? folder.uri.fsPath : folder.uri.fsPath + "/"; + if (uriPath.startsWith(folderPath)) { + let relativePath = uri.fsPath.slice(folder.uri.fsPath.length); + if (relativePath.startsWith("/")) { + relativePath = relativePath.slice(1); + } + return relativePath; + } + } + return uri.fsPath; +} +var numOptionsToShow = 100; +var getAbbreviatedName = (relativePath) => { + return getBasename(relativePath, 1); +}; +var getOptionsAtPath = async (accessor, path, optionText) => { + const toolsService = accessor.get("IToolsService"); + const searchForFilesOrFolders = async (t, searchFor) => { + try { + const searchResults = (await (await toolsService.callTool.search_pathnames_only({ + query: t, + includePattern: null, + pageNumber: 1 + })).result).uris; + if (searchFor === "files") { + const res = searchResults.map((uri) => { + const relativePath = getRelativeWorkspacePath(accessor, uri); + return { + leafNodeType: "File", + uri, + iconInMenu: File, + fullName: relativePath, + abbreviatedName: getAbbreviatedName(relativePath) + }; + }); + return res; + } else if (searchFor === "folders") { + const directoryMap = /* @__PURE__ */ new Map(); + for (const uri of searchResults) { + if (!uri) continue; + const relativePath = getRelativeWorkspacePath(accessor, uri); + const pathParts = relativePath.split("/"); + const workspaceService = accessor.get("IWorkspaceContextService"); + const workspaceFolders = workspaceService.getWorkspace().folders; + let workspaceFolderUri; + if (workspaceFolders.length) { + const sortedFolders = [...workspaceFolders].sort( + (a, b) => b.uri.fsPath.length - a.uri.fsPath.length + ); + for (const folder of sortedFolders) { + const folderPath = folder.uri.fsPath.endsWith("/") ? folder.uri.fsPath : folder.uri.fsPath + "/"; + const uriPath = uri.fsPath.endsWith("/") ? uri.fsPath : uri.fsPath + "/"; + if (uriPath.startsWith(folderPath)) { + workspaceFolderUri = folder.uri; + break; + } + } + } + if (workspaceFolderUri) { + let currentPath = ""; + for (let i = 0; i < pathParts.length - 1; i++) { + currentPath = i === 0 ? `/${pathParts[i]}` : `${currentPath}/${pathParts[i]}`; + const directoryUri = URI.joinPath( + workspaceFolderUri, + currentPath.startsWith("/") ? currentPath.substring(1) : currentPath + ); + directoryMap.set(currentPath, directoryUri); + } + } + } + return Array.from(directoryMap.entries()).map(([relativePath, uri]) => ({ + leafNodeType: "Folder", + uri, + iconInMenu: Folder, + // Folder + fullName: relativePath, + abbreviatedName: getAbbreviatedName(relativePath) + })); + } + } catch (error2) { + console.error("Error fetching directories:", error2); + return []; + } + }; + const allOptions = [ + { + fullName: "selection", + abbreviatedName: "selection", + iconInMenu: File, + generateNextOptions: async (_t) => { + try { + const editorService = accessor.get("IEditorService"); + const languageService = accessor.get("ILanguageService"); + const active = editorService.activeTextEditorControl; + const activeResource = editorService.activeEditor?.resource; + const sel = active?.getSelection?.(); + if (activeResource && sel && !sel.isEmpty()) { + const basename = getAbbreviatedName(getRelativeWorkspacePath(accessor, activeResource)); + const label = `${basename}:${sel.startLineNumber}-${sel.endLineNumber}`; + return [{ + leafNodeType: "File", + uri: activeResource, + range: sel, + iconInMenu: File, + fullName: label, + abbreviatedName: "selection" + }]; + } + } catch { + } + return []; + } + }, + { + fullName: "recent", + abbreviatedName: "recent", + iconInMenu: File, + generateNextOptions: async (t) => { + try { + const historyService = accessor.get("IHistoryService"); + const items = historyService.getHistory().filter((h) => h.resource).map((h) => h.resource); + const options2 = items.map((uri) => { + const relativePath = getRelativeWorkspacePath(accessor, uri); + return { + leafNodeType: "File", + uri, + iconInMenu: File, + fullName: relativePath, + abbreviatedName: getAbbreviatedName(relativePath) + }; + }); + return options2.filter((o) => isSubsequence(o.fullName, t)); + } catch { + return []; + } + } + }, + { + fullName: "workspace", + abbreviatedName: "workspace", + iconInMenu: Folder, + generateNextOptions: async (_t) => { + try { + const workspaceService = accessor.get("IWorkspaceContextService"); + return workspaceService.getWorkspace().folders.map((f) => ({ + leafNodeType: "Folder", + uri: f.uri, + iconInMenu: Folder, + fullName: getRelativeWorkspacePath(accessor, f.uri) || "/", + abbreviatedName: getFolderName(getRelativeWorkspacePath(accessor, f.uri) || "/") + })); + } catch { + return []; + } + } + }, + { + fullName: "files", + abbreviatedName: "files", + iconInMenu: File, + generateNextOptions: async (t) => await searchForFilesOrFolders(t, "files") || [] + }, + { + fullName: "folders", + abbreviatedName: "folders", + iconInMenu: Folder, + generateNextOptions: async (t) => await searchForFilesOrFolders(t, "folders") || [] + } + ]; + let nextOptionsAtPath = allOptions; + let generateNextOptionsAtPath = void 0; + for (const pn of path) { + const selectedOption = nextOptionsAtPath.find((o) => o.fullName.toLowerCase() === pn.toLowerCase()); + if (!selectedOption) return []; + nextOptionsAtPath = selectedOption.nextOptions; + generateNextOptionsAtPath = selectedOption.generateNextOptions; + } + if (generateNextOptionsAtPath) { + nextOptionsAtPath = await generateNextOptionsAtPath(optionText); + } else if (path.length === 0 && optionText.trim().length > 0) { + const filesResults = await searchForFilesOrFolders(optionText, "files") || []; + const foldersResults = await searchForFilesOrFolders(optionText, "folders") || []; + nextOptionsAtPath = [...foldersResults, ...filesResults]; + } + const optionsAtPath = nextOptionsAtPath.filter((o) => isSubsequence(o.fullName, optionText)).sort((a, b) => { + const scoreA = scoreSubsequence(a.fullName, optionText); + const scoreB = scoreSubsequence(b.fullName, optionText); + return scoreB - scoreA; + }).slice(0, numOptionsToShow); + return optionsAtPath; +}; +var VoidInputBox2 = (0, import_react5.forwardRef)(function X2({ initValue, placeholder, multiline, enableAtToMention, fnsRef, className = "", appearance = "default", style, onKeyDown, onFocus, onBlur, onChangeText }, ref) { + const accessor = useAccessor(); + const chatThreadService = accessor.get("IChatThreadService"); + const languageService = accessor.get("ILanguageService"); + const textAreaRef = (0, import_react5.useRef)(null); + const selectedOptionRef = (0, import_react5.useRef)(null); + const [isMenuOpen, _setIsMenuOpen] = (0, import_react5.useState)(false); + const setIsMenuOpen = (value) => { + if (!enableAtToMention) { + return; + } + _setIsMenuOpen(value); + }; + const [optionPath, setOptionPath] = (0, import_react5.useState)([]); + const [optionIdx, setOptionIdx] = (0, import_react5.useState)(0); + const [options2, setOptions2] = (0, import_react5.useState)([]); + const [optionText, setOptionText] = (0, import_react5.useState)(""); + const [didLoadInitialOptions, setDidLoadInitialOptions] = (0, import_react5.useState)(false); + const currentPathRef = (0, import_react5.useRef)(JSON.stringify([])); + const isBreadcrumbsShowing = optionPath.length === 0 && !optionText ? false : true; + const insertTextAtCursor = (text) => { + const textarea = textAreaRef.current; + if (!textarea) return; + textarea.focus(); + const startPos = textarea.selectionStart; + const endPos = textarea.selectionEnd; + const textBeforeCursor = textarea.value.substring(0, startPos - 1); + const textAfterCursor = textarea.value.substring(endPos); + textarea.value = textBeforeCursor + textAfterCursor; + const newCursorPos = textBeforeCursor.length; + textarea.setSelectionRange(newCursorPos, newCursorPos); + if (onChangeText) { + onChangeText(textarea.value); + } + adjustHeight(); + }; + const onSelectOption = async () => { + if (!options2.length) { + return; + } + const option = options2[optionIdx]; + const newPath = [...optionPath, option.fullName]; + const isLastOption = !option.generateNextOptions && !option.nextOptions; + setDidLoadInitialOptions(false); + if (isLastOption) { + setIsMenuOpen(false); + insertTextAtCursor(option.abbreviatedName); + let newSelection; + if (option.leafNodeType === "File") newSelection = { + type: "File", + uri: option.uri, + language: languageService.guessLanguageIdByFilepathOrFirstLine(option.uri) || "", + state: { wasAddedAsCurrentFile: false } + }; + else if (option.leafNodeType === "Folder") newSelection = { + type: "Folder", + uri: option.uri, + language: void 0, + state: void 0 + }; + else + throw new Error(`Unexpected leafNodeType ${option.leafNodeType}`); + chatThreadService.addNewStagingSelection(newSelection); + } else { + currentPathRef.current = JSON.stringify(newPath); + const newOpts = await getOptionsAtPath(accessor, newPath, "") || []; + if (currentPathRef.current !== JSON.stringify(newPath)) { + return; + } + setOptionPath(newPath); + setOptionText(""); + setOptionIdx(0); + setOptions2(newOpts); + setDidLoadInitialOptions(true); + } + }; + const onRemoveOption = async () => { + const newPath = [...optionPath.slice(0, optionPath.length - 1)]; + currentPathRef.current = JSON.stringify(newPath); + const newOpts = await getOptionsAtPath(accessor, newPath, "") || []; + if (currentPathRef.current !== JSON.stringify(newPath)) { + return; + } + setOptionPath(newPath); + setOptionText(""); + setOptionIdx(0); + setOptions2(newOpts); + }; + const onOpenOptionMenu = async () => { + const newPath = []; + currentPathRef.current = JSON.stringify([]); + const newOpts = await getOptionsAtPath(accessor, [], "") || []; + if (currentPathRef.current !== JSON.stringify([])) { + return; + } + setOptionPath(newPath); + setOptionText(""); + setIsMenuOpen(true); + setOptionIdx(0); + setOptions2(newOpts); + }; + const onCloseOptionMenu = () => { + setIsMenuOpen(false); + }; + const onNavigateUp = (step = 1, periodic = true) => { + if (options2.length === 0) return; + setOptionIdx((prevIdx) => { + const newIdx = prevIdx - step; + return periodic ? (newIdx + options2.length) % options2.length : Math.max(0, newIdx); + }); + }; + const onNavigateDown = (step = 1, periodic = true) => { + if (options2.length === 0) return; + setOptionIdx((prevIdx) => { + const newIdx = prevIdx + step; + return periodic ? newIdx % options2.length : Math.min(options2.length - 1, newIdx); + }); + }; + const onNavigateToTop = () => { + if (options2.length === 0) return; + setOptionIdx(0); + }; + const onNavigateToBottom = () => { + if (options2.length === 0) return; + setOptionIdx(options2.length - 1); + }; + const debounceTimerRef = (0, import_react5.useRef)(null); + (0, import_react5.useEffect)(() => { + return () => { + if (debounceTimerRef.current !== null) { + window.clearTimeout(debounceTimerRef.current); + debounceTimerRef.current = null; + } + }; + }, []); + const onPathTextChange = (0, import_react5.useCallback)((newStr) => { + setOptionText(newStr); + if (debounceTimerRef.current !== null) { + window.clearTimeout(debounceTimerRef.current); + } + currentPathRef.current = JSON.stringify(optionPath); + const fetchOptions = async () => { + const newOpts = await getOptionsAtPath(accessor, optionPath, newStr) || []; + if (currentPathRef.current !== JSON.stringify(optionPath)) { + return; + } + setOptions2(newOpts); + setOptionIdx(0); + debounceTimerRef.current = null; + }; + if (newStr.trim() === "") { + fetchOptions(); + } else { + debounceTimerRef.current = window.setTimeout(fetchOptions, 300); + } + }, [optionPath, accessor]); + const onMenuKeyDown = (e) => { + const isCommandKeyPressed = e.altKey || e.ctrlKey || e.metaKey; + if (e.key === "ArrowUp") { + if (isCommandKeyPressed) { + onNavigateToTop(); + } else { + if (e.altKey) { + onNavigateUp(10, false); + } else { + onNavigateUp(); + } + } + } else if (e.key === "ArrowDown") { + if (isCommandKeyPressed) { + onNavigateToBottom(); + } else { + if (e.altKey) { + onNavigateDown(10, false); + } else { + onNavigateDown(); + } + } + } else if (e.key === "ArrowLeft") { + onRemoveOption(); + } else if (e.key === "ArrowRight") { + onSelectOption(); + } else if (e.key === "Enter") { + onSelectOption(); + } else if (e.key === "Escape") { + onCloseOptionMenu(); + } else if (e.key === "Backspace") { + if (!optionText) { + if (optionPath.length === 0) { + onCloseOptionMenu(); + return; + } else { + onRemoveOption(); + } + } else if (isCommandKeyPressed) { + onPathTextChange(""); + } else { + onPathTextChange(optionText.slice(0, -1)); + } + } else if (e.key.length === 1) { + if (isCommandKeyPressed) ; else { + { + onPathTextChange(optionText + e.key); + } + } + } + e.preventDefault(); + e.stopPropagation(); + }; + (0, import_react5.useEffect)(() => { + if (isMenuOpen && selectedOptionRef.current) { + selectedOptionRef.current.scrollIntoView({ + behavior: "instant", + block: "nearest", + inline: "nearest" + }); + } + }, [optionIdx, isMenuOpen, optionText, selectedOptionRef]); + const measureRef = (0, import_react5.useRef)(null); + const gapPx = 2; + const offsetPx = 2; + const { + x, + y, + strategy, + refs, + middlewareData, + update + } = useFloating2({ + open: isMenuOpen, + onOpenChange: setIsMenuOpen, + placement: "bottom", + middleware: [ + offset2({ mainAxis: gapPx, crossAxis: offsetPx }), + flip2({ + boundary: document.body, + padding: 8 + }), + shift2({ + boundary: document.body, + padding: 8 + }), + size2({ + apply({ elements, rects }) { + Object.assign(elements.floating.style, { + width: `${Math.max( + rects.reference.width, + measureRef.current?.offsetWidth ?? 0 + )}px` + }); + }, + padding: 8, + // Use viewport as boundary instead of any parent element + boundary: document.body + }) + ], + whileElementsMounted: autoUpdate, + strategy: "fixed" + }); + (0, import_react5.useEffect)(() => { + if (!isMenuOpen) return; + const handleClickOutside = (event) => { + const target = event.target; + const floating = refs.floating.current; + const reference = refs.reference.current; + const isReferenceHTMLElement = reference && "contains" in reference; + if (floating && (!isReferenceHTMLElement || !reference.contains(target)) && !floating.contains(target)) { + setIsMenuOpen(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [isMenuOpen, refs.floating, refs.reference]); + const [isEnabled, setEnabled] = (0, import_react5.useState)(true); + const adjustHeight = (0, import_react5.useCallback)(() => { + const r = textAreaRef.current; + if (!r) return; + r.style.height = "auto"; + if (r.scrollHeight === 0) return requestAnimationFrame(adjustHeight); + const h = r.scrollHeight; + const newHeight = Math.min(h + 1, 500); + r.style.height = `${newHeight}px`; + }, []); + const fns = (0, import_react5.useMemo)(() => ({ + setValue: (val) => { + const r = textAreaRef.current; + if (!r) return; + r.value = val; + onChangeText?.(r.value); + adjustHeight(); + }, + enable: () => { + setEnabled(true); + }, + disable: () => { + setEnabled(false); + } + }), [onChangeText, adjustHeight]); + (0, import_react5.useEffect)(() => { + if (initValue) + fns.setValue(initValue); + }, [initValue]); + const isChatDark = appearance === "chatDark"; + const appearanceClasses = isChatDark ? "text-white placeholder:text-white/40" : "text-void-fg-1 placeholder:text-void-fg-3"; + const baseStyle = isChatDark ? { + background: "transparent", + color: "#fff", + border: "none", + boxShadow: "none" + } : { + background: asCssVariable(inputBackground), + color: asCssVariable(inputForeground) + }; + return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "textarea", + { + autoFocus: false, + ref: (0, import_react5.useCallback)((r) => { + if (fnsRef) + fnsRef.current = fns; + refs.setReference(r); + textAreaRef.current = r; + if (typeof ref === "function") ref(r); + else if (ref) ref.current = r; + adjustHeight(); + }, [fnsRef, fns, setEnabled, adjustHeight, ref, refs]), + onFocus, + onBlur, + disabled: !isEnabled, + className: `void-w-full void-resize-none void-max-h-[500px] void-overflow-y-auto ${appearanceClasses} ${className}`, + style: { ...baseStyle, ...style }, + onInput: (0, import_react5.useCallback)((event) => { + const latestChange = event.nativeEvent.data; + if (latestChange === "@") { + onOpenOptionMenu(); + } + }, [onOpenOptionMenu, accessor]), + onChange: (0, import_react5.useCallback)((e) => { + const r = textAreaRef.current; + if (!r) return; + onChangeText?.(r.value); + adjustHeight(); + }, [onChangeText, adjustHeight]), + onKeyDown: (0, import_react5.useCallback)((e) => { + if (isMenuOpen) { + onMenuKeyDown(e); + return; + } + if (e.key === "Backspace") { + if (!e.currentTarget.value || e.currentTarget.selectionStart === 0 && e.currentTarget.selectionEnd === 0) { + if (e.metaKey || e.ctrlKey) { + chatThreadService.popStagingSelections(Number.MAX_SAFE_INTEGER); + } else { + chatThreadService.popStagingSelections(1); + } + return; + } + } + if (e.key === "Enter") { + const shouldAddNewline = e.shiftKey && multiline; + if (!shouldAddNewline) e.preventDefault(); + } + onKeyDown?.(e); + }, [onKeyDown, onMenuKeyDown, multiline]), + rows: 1, + placeholder + } + ), + isMenuOpen && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( + "div", + { + ref: refs.setFloating, + className: "void-z-[100] void-border-void-border-3 void-bg-void-bg-2-alt void-border void-rounded void-shadow-lg void-flex void-flex-col void-overflow-hidden", + style: { + position: strategy, + top: y ?? 0, + left: x ?? 0, + width: refs.reference.current instanceof HTMLElement ? refs.reference.current.offsetWidth : 0 + }, + onWheel: (e) => e.stopPropagation(), + children: [ + isBreadcrumbsShowing && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-px-2 void-py-1 void-text-void-fg-1 void-bg-void-bg-2-alt void-border-b void-border-void-border-3 void-sticky void-top-0 void-bg-void-bg-1 void-z-10 void-select-none void-pointer-events-none", children: optionText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-flex void-items-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: optionText }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-opacity-50", children: "Enter text to filter..." }) }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-max-h-[400px] void-w-full void-max-w-full void-overflow-y-auto void-overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-w-max void-min-w-full void-flex void-flex-col void-gap-0 void-text-nowrap void-flex-nowrap", children: options2.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-text-void-fg-3 void-px-3 void-py-0.5", children: "No results found" }) : options2.map((o, oIdx) => { + return ( + // Option + /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( + "div", + { + ref: oIdx === optionIdx ? selectedOptionRef : null, + className: ` void-flex void-items-center void-gap-2 void-px-3 void-py-1 void-cursor-pointer ${oIdx === optionIdx ? "void-bg-blue-500 void-text-white/80" : "void-bg-void-bg-2-alt void-text-void-fg-1"} `, + onClick: () => { + onSelectOption(); + }, + onMouseMove: () => { + setOptionIdx(oIdx); + }, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(o.iconInMenu, { size: 12 }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: o.abbreviatedName }), + o.fullName && o.fullName !== o.abbreviatedName && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "void-opacity-60 void-text-sm", children: o.fullName }), + o.nextOptions || o.generateNextOptions ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronRight, { size: 12 }) : null + ] + }, + o.fullName + ) + ); + }) }) }) + ] + } + ) + ] }); +}); +var VoidSimpleInputBox = ({ + value, + onChangeValue, + placeholder, + className, + disabled, + passwordBlur, + compact, + ...inputProps +}) => { + const inputRef = (0, import_react5.useRef)(null); + const selectionRef = (0, import_react5.useRef)({ + start: null, + end: null + }); + (0, import_react5.useEffect)(() => { + const input = inputRef.current; + if (input && input.value !== value) { + selectionRef.current.start = input.selectionStart; + selectionRef.current.end = input.selectionEnd; + input.value = value; + if (selectionRef.current.start !== null && selectionRef.current.end !== null) { + input.setSelectionRange(selectionRef.current.start, selectionRef.current.end); + } + } + }, [value]); + const handleChange = (0, import_react5.useCallback)((e) => { + onChangeValue(e.target.value); + }, [onChangeValue]); + return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "input", + { + ref: inputRef, + defaultValue: value, + onChange: handleChange, + placeholder, + disabled, + className: `void-w-full void-resize-none void-bg-void-bg-1 void-text-void-fg-1 placeholder:void-text-void-fg-3 void-border void-border-void-border-2 focus:void-border-void-border-1 ${compact ? "void-py-1 void-px-2" : "void-py-2 void-px-4 "} void-rounded ${disabled ? "void-opacity-50 void-cursor-not-allowed" : ""} ${className}`, + style: { + ...passwordBlur && { WebkitTextSecurity: "disc" }, + background: asCssVariable(inputBackground), + color: asCssVariable(inputForeground) + }, + ...inputProps, + type: void 0 + } + ); +}; +var VoidSlider = ({ + value, + onChange, + size: size3 = "md", + disabled = false, + min = 0, + max = 7, + step = 1, + className = "", + width = 200 +}) => { + const percentage = (value - min) / (max - min) * 100; + const handleTrackClick = (e) => { + if (disabled) return; + const rect = e.currentTarget.getBoundingClientRect(); + const clickPosition = e.clientX - rect.left; + const trackWidth = rect.width; + const newPercentage = Math.max(0, Math.min(1, clickPosition / trackWidth)); + const rawValue = min + newPercentage * (max - min); + if (rawValue >= max - step / 2) { + onChange(max); + return; + } + const steppedValue = Math.round((rawValue - min) / step) * step + min; + const clampedValue = Math.max(min, Math.min(max, steppedValue)); + onChange(clampedValue); + }; + const handleThumbDrag = (moveEvent, track) => { + if (!track) return; + const rect = track.getBoundingClientRect(); + const movePosition = moveEvent.clientX - rect.left; + const trackWidth = rect.width; + const newPercentage = Math.max(0, Math.min(1, movePosition / trackWidth)); + const rawValue = min + newPercentage * (max - min); + if (rawValue >= max - step / 2) { + onChange(max); + return; + } + const steppedValue = Math.round((rawValue - min) / step) * step + min; + const clampedValue = Math.max(min, Math.min(max, steppedValue)); + onChange(clampedValue); + }; + return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `void-inline-flex void-items-center void-flex-shrink-0 ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "div", + { + className: `void-relative void-flex-shrink-0 ${disabled ? "void-opacity-25" : ""}`, + style: { + width + // Add horizontal padding equal to half the thumb width + // paddingLeft: thumbSizePx / 2, + // paddingRight: thumbSizePx / 2 + }, + children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "void-relative void-w-full", children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "div", + { + className: "void-absolute void-w-full void-cursor-pointer", + style: { + height: "16px", + top: "50%", + transform: "translateY(-50%)", + zIndex: 1 + }, + onClick: handleTrackClick + } + ), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "div", + { + className: `void-relative ${size3 === "xxs" ? "void-h-0.5" : size3 === "xs" ? "void-h-1" : size3 === "sm" ? "void-h-1.5" : size3 === "sm+" ? "void-h-2" : "void-h-2.5"} void-bg-void-bg-2 void-rounded-full void-cursor-pointer`, + onClick: handleTrackClick, + children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "div", + { + className: `void-absolute void-left-0 ${size3 === "xxs" ? "void-h-0.5" : size3 === "xs" ? "void-h-1" : size3 === "sm" ? "void-h-1.5" : size3 === "sm+" ? "void-h-2" : "void-h-2.5"} void-bg-void-fg-1 void-rounded-full`, + style: { width: `${percentage}%` } + } + ) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "div", + { + className: `void-absolute void-top-1/2 void-transform -void-translate-x-1/2 -void-translate-y-1/2 ${size3 === "xxs" ? "void-h-2 void-w-2" : size3 === "xs" ? "void-h-2.5 void-w-2.5" : size3 === "sm" ? "void-h-3 void-w-3" : size3 === "sm+" ? "void-h-3.5 void-w-3.5" : "void-h-4 void-w-4"} void-bg-void-fg-1 void-rounded-full void-shadow-md ${disabled ? "void-cursor-not-allowed" : "void-cursor-grab active:void-cursor-grabbing"} void-border void-border-void-fg-1`, + style: { left: `${percentage}%`, zIndex: 2 }, + onMouseDown: (e) => { + if (disabled) return; + const track = e.currentTarget.previousElementSibling; + const handleMouseMove = (moveEvent) => { + handleThumbDrag(moveEvent, track); + }; + const handleMouseUp = () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + }; + document.body.style.userSelect = "none"; + document.body.style.cursor = "grabbing"; + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + e.preventDefault(); + } + } + ) + ] }) + } + ) }); +}; +var VoidSwitch = ({ + value, + onChange, + size: size3 = "md", + disabled = false, + ...props +}) => { + return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "void-inline-flex void-items-center", ...props, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "div", + { + onClick: () => !disabled && onChange(!value), + className: ` void-cursor-pointer void-relative void-inline-flex void-items-center void-rounded-full void-transition-colors void-duration-200 void-ease-in-out ${value ? "void-bg-zinc-900 dark:void-bg-white" : "void-bg-white dark:void-bg-zinc-600"} ${disabled ? "void-opacity-25" : ""} ${size3 === "xxs" ? "void-h-3 void-w-5" : ""} ${size3 === "xs" ? "void-h-4 void-w-7" : ""} ${size3 === "sm" ? "void-h-5 void-w-9" : ""} ${size3 === "sm+" ? "void-h-5 void-w-10" : ""} ${size3 === "md" ? "void-h-6 void-w-11" : ""} `, + children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "span", + { + className: ` void-inline-block void-transform void-rounded-full void-bg-white dark:void-bg-zinc-900 void-shadow void-transition-transform void-duration-200 void-ease-in-out ${size3 === "xxs" ? "void-h-2 void-w-2" : ""} ${size3 === "xs" ? "void-h-2.5 void-w-2.5" : ""} ${size3 === "sm" ? "void-h-3 void-w-3" : ""} ${size3 === "sm+" ? "void-h-3.5 void-w-3.5" : ""} ${size3 === "md" ? "void-h-4 void-w-4" : ""} ${size3 === "xxs" ? value ? "void-translate-x-2.5" : "void-translate-x-0.5" : ""} ${size3 === "xs" ? value ? "void-translate-x-3.5" : "void-translate-x-0.5" : ""} ${size3 === "sm" ? value ? "void-translate-x-5" : "void-translate-x-1" : ""} ${size3 === "sm+" ? value ? "void-translate-x-6" : "void-translate-x-1" : ""} ${size3 === "md" ? value ? "void-translate-x-6" : "void-translate-x-1" : ""} ` + } + ) + } + ) }); +}; +var VoidCustomDropdownBox = ({ + options: options2, + selectedOption, + onChangeOption, + getOptionDropdownName, + getOptionDropdownDetail, + getOptionDisplayName, + getOptionsEqual, + className, + arrowTouchesText = true, + matchInputWidth = false, + gapPx = 0, + offsetPx = -6 +}) => { + const [isOpen, setIsOpen] = (0, import_react5.useState)(false); + const measureRef = (0, import_react5.useRef)(null); + const { + x, + y, + strategy, + refs, + middlewareData, + update + } = useFloating2({ + open: isOpen, + onOpenChange: setIsOpen, + placement: "bottom-start", + middleware: [ + offset2({ mainAxis: gapPx, crossAxis: offsetPx }), + flip2({ + boundary: document.body, + padding: 8 + }), + shift2({ + boundary: document.body, + padding: 8 + }), + size2({ + apply({ availableHeight, elements, rects }) { + const maxHeight = Math.min(availableHeight); + Object.assign(elements.floating.style, { + maxHeight: `${maxHeight}px`, + overflowY: "auto", + // Ensure the width isn't constrained by the parent + width: `${Math.max( + rects.reference.width, + measureRef.current?.offsetWidth ?? 0 + )}px` + }); + }, + padding: 8, + // Use viewport as boundary instead of any parent element + boundary: document.body + }) + ], + whileElementsMounted: autoUpdate, + strategy: "fixed" + }); + (0, import_react5.useEffect)(() => { + if (options2.length === 0) return; + if (selectedOption !== void 0) return; + onChangeOption(options2[0]); + }, [selectedOption, onChangeOption, options2]); + (0, import_react5.useEffect)(() => { + if (!isOpen) return; + const handleClickOutside = (event) => { + const target = event.target; + const floating = refs.floating.current; + const reference = refs.reference.current; + const isReferenceHTMLElement = reference && "contains" in reference; + if (floating && (!isReferenceHTMLElement || !reference.contains(target)) && !floating.contains(target)) { + setIsOpen(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [isOpen, refs.floating, refs.reference]); + if (selectedOption === void 0) + return null; + return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `void-inline-block void-relative ${className}`, children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "div", + { + ref: measureRef, + className: "void-opacity-0 void-pointer-events-none void-absolute -void-left-[999999px] -void-top-[999999px] void-flex void-flex-col", + "aria-hidden": "true", + children: options2.map((option) => { + const optionName = getOptionDropdownName(option); + const optionDetail = getOptionDropdownDetail?.(option) || ""; + return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "void-flex void-items-center void-whitespace-nowrap", children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-w-4" }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "void-flex void-justify-between void-w-full", children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: optionName }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: optionDetail }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "______" }) + ] }) + ] }, optionName + optionDetail); + }) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( + "button", + { + type: "button", + ref: refs.setReference, + className: "void-flex void-items-center void-h-4 void-bg-transparent void-whitespace-nowrap hover:void-brightness-90 void-w-full", + onClick: () => setIsOpen(!isOpen), + children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `void-truncate ${arrowTouchesText ? "void-mr-1" : ""}`, children: getOptionDisplayName(selectedOption) }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "svg", + { + className: `void-size-3 void-flex-shrink-0 ${arrowTouchesText ? "" : "void-ml-auto"}`, + viewBox: "0 0 12 12", + fill: "none", + children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "path", + { + d: "M2.5 4.5L6 8L9.5 4.5", + stroke: "currentColor", + strokeWidth: "1.5", + strokeLinecap: "round", + strokeLinejoin: "round" + } + ) + } + ) + ] + } + ), + isOpen && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "div", + { + ref: refs.setFloating, + className: "void-z-[100] void-bg-void-bg-1 void-border-void-border-3 void-border void-rounded void-shadow-lg", + style: { + position: strategy, + top: y ?? 0, + left: x ?? 0, + width: matchInputWidth ? refs.reference.current instanceof HTMLElement ? refs.reference.current.offsetWidth : 0 : Math.max( + refs.reference.current instanceof HTMLElement ? refs.reference.current.offsetWidth : 0, + measureRef.current instanceof HTMLElement ? measureRef.current.offsetWidth : 0 + ) + }, + onWheel: (e) => e.stopPropagation(), + children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-overflow-auto void-max-h-80", children: options2.map((option) => { + const thisOptionIsSelected = getOptionsEqual(option, selectedOption); + const optionName = getOptionDropdownName(option); + const optionDetail = getOptionDropdownDetail?.(option) || ""; + return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( + "div", + { + className: `void-flex void-items-center void-px-2 void-py-1 void-pr-4 void-cursor-pointer void-whitespace-nowrap void-transition-all void-duration-100 ${thisOptionIsSelected ? "void-bg-blue-500 void-text-white/80" : "hover:void-bg-blue-500 hover:void-text-white/80"} `, + onClick: () => { + onChangeOption(option); + setIsOpen(false); + }, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-w-4 void-flex void-justify-center void-flex-shrink-0", children: thisOptionIsSelected && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "void-size-3", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "path", + { + d: "M10 3L4.5 8.5L2 6", + stroke: "currentColor", + strokeWidth: "1.5", + strokeLinecap: "round", + strokeLinejoin: "round" + } + ) }) }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "void-flex void-justify-between void-items-center void-w-full void-gap-x-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: optionName }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "void-opacity-60", children: optionDetail }) + ] }) + ] + }, + optionName + ); + }) }) + } + ) + ] }); +}; +var normalizeIndentation = (code) => { + const lines = code.split("\n"); + let minLeadingSpaces = Infinity; + for (const line of lines) { + if (line.trim() === "") continue; + let leadingSpaces = 0; + for (let i = 0; i < line.length; i++) { + const char = line[i]; + if (char === " " || char === " ") { + leadingSpaces += 1; + } else { + break; + } + } + minLeadingSpaces = Math.min(minLeadingSpaces, leadingSpaces); + } + return lines.map((line) => { + if (line.trim() === "") return line; + let spacesToRemove = minLeadingSpaces; + let i = 0; + while (spacesToRemove > 0 && i < line.length) { + const char = line[i]; + if (char === " " || char === " ") { + spacesToRemove -= 1; + i++; + } else { + break; + } + } + return line.slice(i); + }).join("\n"); +}; +var modelOfEditorId = {}; +var BlockCode = ({ initValue, language, maxHeight, showScrollbars }) => { + initValue = normalizeIndentation(initValue); + const MAX_HEIGHT = maxHeight ?? Infinity; + const SHOW_SCROLLBARS = showScrollbars ?? false; + const divRef = (0, import_react5.useRef)(null); + const accessor = useAccessor(); + const instantiationService = accessor.get("IInstantiationService"); + const modelService = accessor.get("IModelService"); + const id = (0, import_react5.useId)(); + const initValueRef = (0, import_react5.useRef)(initValue); + const languageRef = (0, import_react5.useRef)(language); + const modelRef = (0, import_react5.useRef)(null); + (0, import_react5.useEffect)(() => { + initValueRef.current = initValue; + modelRef.current?.setValue(initValue); + }, [initValue]); + (0, import_react5.useEffect)(() => { + languageRef.current = language; + if (language) modelRef.current?.setLanguage(language); + }, [language]); + return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: divRef, className: "void-relative void-z-0 void-px-2 void-py-1 void-bg-void-bg-3", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + WidgetComponent, + { + className: "bg-editor-style-override", + ctor: (0, import_react5.useCallback)((container) => { + return instantiationService.createInstance( + CodeEditorWidget, + container, + { + automaticLayout: true, + wordWrap: "off", + scrollbar: { + alwaysConsumeMouseWheel: false, + ...SHOW_SCROLLBARS ? { + vertical: "auto", + verticalScrollbarSize: 8, + horizontal: "auto", + horizontalScrollbarSize: 8 + } : { + vertical: "hidden", + verticalScrollbarSize: 0, + horizontal: "auto", + horizontalScrollbarSize: 8, + ignoreHorizontalScrollbarInContentHeight: true + } + }, + scrollBeyondLastLine: false, + lineNumbers: "off", + readOnly: true, + domReadOnly: true, + readOnlyMessage: { value: "" }, + minimap: { + enabled: false + // maxColumn: 0, + }, + hover: { enabled: false }, + selectionHighlight: false, + // highlights whole words + renderLineHighlight: "none", + folding: false, + lineDecorationsWidth: 0, + overviewRulerLanes: 0, + hideCursorInOverviewRuler: true, + overviewRulerBorder: false, + glyphMargin: false, + stickyScroll: { + enabled: false + } + }, + { + isSimpleWidget: true + } + ); + }, [instantiationService]), + onCreateInstance: (0, import_react5.useCallback)((editor) => { + const languageId = languageRef.current ? languageRef.current : "plaintext"; + const model = modelOfEditorId[id] ?? modelService.createModel( + initValueRef.current, + { + languageId, + onDidChange: (e) => { + return { dispose: () => { + } }; + } + // no idea why they'd require this + } + ); + modelRef.current = model; + editor.setModel(model); + const container = editor.getDomNode(); + const parentNode = container?.parentElement; + const resize = () => { + const height = editor.getScrollHeight() + 1; + if (parentNode) { + parentNode.style.height = `${height}px`; + parentNode.style.maxHeight = `${MAX_HEIGHT}px`; + editor.layout(); + } + }; + resize(); + const disposable = editor.onDidContentSizeChange(() => { + resize(); + }); + return [disposable, model]; + }, [modelService]), + dispose: (0, import_react5.useCallback)((editor) => { + editor.dispose(); + }, [modelService]), + propsFn: (0, import_react5.useCallback)(() => { + return []; + }, []) + } + ) }); +}; +var VoidButtonBgDarken = ({ children, disabled, onClick, className }) => { + return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "button", + { + disabled, + className: `void-px-3 void-py-1 void-bg-black/10 dark:void-bg-white/10 void-rounded-sm void-overflow-hidden void-whitespace-nowrap void-flex void-items-center void-justify-center ${className || ""}`, + onClick, + children + } + ); +}; +var SingleDiffEditor = ({ block: block2, lang }) => { + const accessor = useAccessor(); + const modelService = accessor.get("IModelService"); + const instantiationService = accessor.get("IInstantiationService"); + const languageService = accessor.get("ILanguageService"); + const languageSelection = (0, import_react5.useMemo)(() => languageService.createById(lang), [lang, languageService]); + const originalModel = (0, import_react5.useMemo)( + () => modelService.createModel(block2.orig, languageSelection), + [block2.orig, languageSelection, modelService] + ); + const modifiedModel = (0, import_react5.useMemo)( + () => modelService.createModel(block2.final, languageSelection), + [block2.final, languageSelection, modelService] + ); + (0, import_react5.useEffect)(() => { + return () => { + originalModel.dispose(); + modifiedModel.dispose(); + }; + }, [originalModel, modifiedModel]); + const divRef = (0, import_react5.useRef)(null); + const editorRef = (0, import_react5.useRef)(null); + (0, import_react5.useEffect)(() => { + if (!divRef.current) return; + const editor = instantiationService.createInstance( + DiffEditorWidget, + divRef.current, + { + automaticLayout: true, + readOnly: true, + renderSideBySide: true, + minimap: { enabled: false }, + lineNumbers: "off", + scrollbar: { + vertical: "hidden", + horizontal: "auto", + verticalScrollbarSize: 0, + horizontalScrollbarSize: 8, + alwaysConsumeMouseWheel: false, + ignoreHorizontalScrollbarInContentHeight: true + }, + hover: { enabled: false }, + folding: false, + selectionHighlight: false, + renderLineHighlight: "none", + overviewRulerLanes: 0, + hideCursorInOverviewRuler: true, + overviewRulerBorder: false, + glyphMargin: false, + stickyScroll: { enabled: false }, + scrollBeyondLastLine: false, + renderGutterMenu: false, + renderIndicators: false + }, + { originalEditor: { isSimpleWidget: true }, modifiedEditor: { isSimpleWidget: true } } + ); + editor.setModel({ original: originalModel, modified: modifiedModel }); + const updateHeight = () => { + const contentHeight = Math.max( + originalModel.getLineCount() * 19, + // approximate line height + modifiedModel.getLineCount() * 19 + ) + 19 * 2 + 1; + const height = Math.min(Math.max(contentHeight, 100), 300); + if (divRef.current) { + divRef.current.style.height = `${height}px`; + editor.layout(); + } + }; + updateHeight(); + editorRef.current = editor; + const disposable1 = originalModel.onDidChangeContent(() => updateHeight()); + const disposable2 = modifiedModel.onDidChangeContent(() => updateHeight()); + return () => { + disposable1.dispose(); + disposable2.dispose(); + editor.dispose(); + editorRef.current = null; + }; + }, [originalModel, modifiedModel, instantiationService]); + return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-w-full void-bg-void-bg-3 bg-editor-style-override", ref: divRef }); +}; +var VoidDiffEditor = ({ uri, searchReplaceBlocks, language }) => { + const accessor = useAccessor(); + const languageService = accessor.get("ILanguageService"); + const blocks = extractSearchReplaceBlocks(searchReplaceBlocks); + let lang = language; + if (!lang && blocks.length > 0) { + lang = detectLanguage(languageService, { uri: uri ?? null, fileContents: blocks[0].orig }); + } + if (blocks.length === 0) { + return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-w-full void-p-4 void-text-void-fg-4 void-text-sm", children: "No changes found" }); + } + return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "void-w-full void-flex void-flex-col void-gap-2", children: blocks.map( + (block2, index3) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "void-w-full", children: [ + blocks.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "void-text-void-fg-4 void-text-xs void-mb-1 void-px-1 void-void-diff-block-header", children: [ + "Change ", + index3 + 1, + " of ", + blocks.length + ] }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SingleDiffEditor, { block: block2, lang }) + ] }, index3) + ) }); +}; + +// src2/markdown/ChatMarkdownRender.tsx +var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1); +var getApplyBoxId = ({ threadId, messageIdx, tokenIdx }) => { + return `${threadId}-${messageIdx}-${tokenIdx}`; +}; +function isValidUri(s) { + return s.length > 5 && isAbsolute(s) && !s.includes("//") && !s.includes("/*"); +} +var LatexRender = ({ latex }) => { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "void-katex-error void-text-red-500", children: latex }); +}; +var Codespan = ({ text, className, onClick, tooltip }) => { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + "code", + { + className: `void-font-mono void-font-medium void-rounded-sm void-bg-void-bg-1 void-px-1 ${className}`, + onClick, + ...tooltip ? { + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": tooltip, + "data-tooltip-place": "top" + } : {}, + children: text + } + ); +}; +var CodespanWithLink = ({ text, rawText, chatMessageLocation }) => { + const accessor = useAccessor(); + const chatThreadService = accessor.get("IChatThreadService"); + accessor.get("ICommandService"); + accessor.get("ICodeEditorService"); + const { messageIdx, threadId } = chatMessageLocation; + const [didComputeCodespanLink, setDidComputeCodespanLink] = (0, import_react7.useState)(false); + let link2 = void 0; + let tooltip = void 0; + let displayText = text; + if (rawText.endsWith("`")) { + link2 = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId }); + if (link2 === void 0) { + chatThreadService.generateCodespanLink({ codespanStr: text, threadId }).then((link3) => { + chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link3, messageIdx, threadId }); + setDidComputeCodespanLink(true); + }); + } + if (link2?.displayText) { + displayText = link2.displayText; + } + if (isValidUri(displayText)) { + tooltip = getRelative(URI.file(displayText), accessor); + displayText = getBasename(displayText); + } + } + const onClick = () => { + if (!link2) return; + if (link2.selection) + voidOpenFileFn(link2.uri, accessor, [link2.selection.startLineNumber, link2.selection.endLineNumber]); + else + voidOpenFileFn(link2.uri, accessor); + }; + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + Codespan, + { + text: displayText, + onClick, + className: link2 ? "void-underline hover:void-brightness-90 void-transition-all void-duration-200 void-cursor-pointer" : "", + tooltip: tooltip || void 0 + } + ); +}; +var paragraphToLatexSegments = (paragraphText) => { + const segments = []; + if (paragraphText && !(paragraphText.includes("#") || paragraphText.includes("`")) && !/^[\w\s.()[\]{}]+$/.test(paragraphText)) { + const rawText = paragraphText; + const displayMathRegex = /\$\$(.*?)\$\$/g; + const inlineMathRegex = /\$((?!\$).*?)\$/g; + if (displayMathRegex.test(rawText) || inlineMathRegex.test(rawText)) { + displayMathRegex.lastIndex = 0; + inlineMathRegex.lastIndex = 0; + let lastIndex = 0; + let segmentId = 0; + let match; + displayMathRegex.lastIndex = 0; + while ((match = displayMathRegex.exec(rawText)) !== null) { + const [fullMatch, formula] = match; + const matchIndex = match.index; + if (matchIndex > lastIndex) { + const textBefore = rawText.substring(lastIndex, matchIndex); + segments.push( + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: textBefore }, `text-${segmentId++}`) + ); + } + segments.push( + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LatexRender, { latex: fullMatch }, `latex-${segmentId++}`) + ); + lastIndex = matchIndex + fullMatch.length; + } + if (lastIndex < rawText.length) { + const remainingText = rawText.substring(lastIndex); + lastIndex = 0; + inlineMathRegex.lastIndex = 0; + const inlineSegments = []; + while ((match = inlineMathRegex.exec(remainingText)) !== null) { + const [fullMatch] = match; + const matchIndex = match.index; + if (matchIndex > lastIndex) { + const textBefore = remainingText.substring(lastIndex, matchIndex); + inlineSegments.push( + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: textBefore }, `inline-text-${segmentId++}`) + ); + } + inlineSegments.push( + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LatexRender, { latex: fullMatch }, `inline-latex-${segmentId++}`) + ); + lastIndex = matchIndex + fullMatch.length; + } + if (lastIndex < remainingText.length) { + inlineSegments.push( + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: remainingText.substring(lastIndex) }, `inline-final-${segmentId++}`) + ); + } + segments.push(...inlineSegments); + } + } + } + return segments; +}; +var RenderToken = ({ token, inPTag, codeURI, chatMessageLocation, tokenIdx, ...options2 }) => { + const accessor = useAccessor(); + const languageService = accessor.get("ILanguageService"); + const t = token; + if (t.raw.trim() === "") { + return null; + } + if (t.type === "space") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t.raw }); + } + if (t.type === "code") { + const [firstLine, remainingContents] = separateOutFirstLine(t.text); + const firstLineIsURI = isValidUri(firstLine) && !codeURI; + let contents = firstLineIsURI ? remainingContents?.trimStart() || "" : t.text; + if (!contents) return null; + const secretDetectionService = accessor.get("ISecretDetectionService"); + const config = secretDetectionService.getConfig(); + if (config.enabled) { + const detection = secretDetectionService.detectSecrets(contents); + contents = detection.redactedText; + } + let uri; + let language; + if (codeURI) { + uri = codeURI; + } else if (firstLineIsURI) { + uri = URI.file(firstLine); + } else { + uri = null; + } + if (t.lang) { + language = convertToVscodeLang(languageService, t.lang); + } else { + language = detectLanguage(languageService, { uri, fileContents: contents }); + } + if (options2.isApplyEnabled && chatMessageLocation) { + const isCodeblockClosed = t.raw.trimEnd().endsWith("```"); + const applyBoxId = getApplyBoxId({ + threadId: chatMessageLocation.threadId, + messageIdx: chatMessageLocation.messageIdx, + tokenIdx + }); + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + BlockCodeApplyWrapper, + { + canApply: isCodeblockClosed, + applyBoxId, + codeStr: contents, + language, + uri: uri || "current", + children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + BlockCode, + { + initValue: contents.trimEnd(), + language + } + ) + } + ); + } + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + BlockCode, + { + initValue: contents, + language + } + ); + } + if (t.type === "heading") { + const HeadingTag = `h${t.depth}`; + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeadingTag, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChatMarkdownRender, { chatMessageLocation, string: t.text, inPTag: true, codeURI, ...options2 }) }); + } + if (t.type === "table") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("table", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tr", { children: t.header.map( + (h, hIdx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: h.text }, hIdx) + ) }) }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: t.rows.map( + (row, rowIdx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tr", { children: row.map( + (r, rIdx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: r.text }, rIdx) + ) }, rowIdx) + ) }) + ] }) }); + } + if (t.type === "hr") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("hr", {}); + } + if (t.type === "blockquote") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("blockquote", { children: t.text }); + } + if (t.type === "list_item") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: t.checked, readOnly: true }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChatMarkdownRender, { chatMessageLocation, string: t.text, inPTag: true, codeURI, ...options2 }) }) + ] }); + } + if (t.type === "list") { + const ListTag = t.ordered ? "ol" : "ul"; + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ListTag, { start: t.start ? t.start : void 0, children: t.items.map( + (item, index3) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { children: [ + item.task && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: item.checked, readOnly: true }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChatMarkdownRender, { chatMessageLocation, string: item.text, inPTag: true, ...options2 }) }) + ] }, index3) + ) }); + } + if (t.type === "paragraph") { + const latexSegments = paragraphToLatexSegments(t.raw); + if (latexSegments.length !== 0) { + if (inPTag) { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "void-block", children: latexSegments }); + } + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: latexSegments }); + } + const contents = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: t.tokens.map( + (token2, index3) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + RenderToken, + { + token: token2, + tokenIdx: `${tokenIdx ? `${tokenIdx}-` : ""}${index3}`, + chatMessageLocation, + inPTag: true, + ...options2 + }, + index3 + ) + ) }); + if (inPTag) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "void-block", children: contents }); + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: contents }); + } + if (t.type === "text" || t.type === "escape" || t.type === "html") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: t.raw }); + } + if (t.type === "def") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, {}); + } + if (t.type === "link") { + const secretDetectionService = accessor.get("ISecretDetectionService"); + const config = secretDetectionService.getConfig(); + let href = t.href; + let text = t.text; + if (config.enabled) { + const hrefDetection = secretDetectionService.detectSecrets(href); + href = hrefDetection.redactedText; + const textDetection = secretDetectionService.detectSecrets(text); + text = textDetection.redactedText; + } + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + "a", + { + onClick: () => { + window.open(href); + }, + href, + title: t.title ?? void 0, + className: "void-underline void-cursor-pointer hover:void-brightness-90 void-transition-all void-duration-200 void-text-void-fg-2", + children: text + } + ); + } + if (t.type === "image") { + const secretDetectionService = accessor.get("ISecretDetectionService"); + const config = secretDetectionService.getConfig(); + let src = t.href; + let alt = t.text; + if (config.enabled) { + const srcDetection = secretDetectionService.detectSecrets(src); + src = srcDetection.redactedText; + const altDetection = secretDetectionService.detectSecrets(alt); + alt = altDetection.redactedText; + } + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + "img", + { + src, + alt, + title: t.title ?? void 0 + } + ); + } + if (t.type === "strong") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: t.text }); + } + if (t.type === "em") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("em", { children: t.text }); + } + if (t.type === "codespan") { + const secretDetectionService = accessor.get("ISecretDetectionService"); + const config = secretDetectionService.getConfig(); + let text = t.text; + if (config.enabled) { + const detection = secretDetectionService.detectSecrets(text); + text = detection.redactedText; + } + if (options2.isLinkDetectionEnabled && chatMessageLocation) { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + CodespanWithLink, + { + text, + rawText: t.raw, + chatMessageLocation + } + ); + } + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Codespan, { text }); + } + if (t.type === "br") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("br", {}); + } + if (t.type === "del") { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("del", { children: t.text }); + } + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "void-bg-orange-50 void-rounded-sm void-overflow-hidden void-p-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "void-text-sm void-text-orange-500", children: "Unknown token rendered..." }) }); +}; +var ChatMarkdownRender = ({ string, inPTag = false, chatMessageLocation, ...options2 }) => { + const accessor = useAccessor(); + const secretDetectionService = accessor.get("ISecretDetectionService"); + const redactedString = (0, import_react7.useMemo)(() => { + const config = secretDetectionService.getConfig(); + if (!config.enabled) { + return string.replaceAll("\n\u2022", "\n\n\u2022"); + } + const detection = secretDetectionService.detectSecrets(string); + return detection.redactedText.replaceAll("\n\u2022", "\n\n\u2022"); + }, [string, secretDetectionService]); + const [debouncedString, setDebouncedString] = (0, import_react7.useState)(redactedString); + const rafRef = (0, import_react7.useRef)(); + const lastUpdateRef = (0, import_react7.useRef)(redactedString); + (0, import_react7.useEffect)(() => { + lastUpdateRef.current = redactedString; + if (redactedString.length < 500) { + setDebouncedString(redactedString); + return; + } + if (rafRef.current) { + cancelAnimationFrame(rafRef.current); + } + rafRef.current = requestAnimationFrame(() => { + setDebouncedString(lastUpdateRef.current); + rafRef.current = void 0; + }); + return () => { + if (rafRef.current) { + cancelAnimationFrame(rafRef.current); + } + }; + }, [redactedString]); + const tokens = (0, import_react7.useMemo)(() => { + if (debouncedString.length > 1e4) { + try { + return marked.lexer(debouncedString, { async: false }); + } catch (e) { + return []; + } + } + return marked.lexer(debouncedString); + }, [debouncedString]); + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: tokens.map( + (token, index3) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RenderToken, { token, inPTag, chatMessageLocation, tokenIdx: index3 + "", ...options2 }, index3) + ) }); +}; + +// src2/sidebar-tsx/ErrorDisplay.tsx +var import_react8 = __toESM(require_react(), 1); +var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1); +var ErrorDisplay = ({ + message: message_, + fullError, + onDismiss, + showDismiss, + onRetry, + onRollback, + onOpenLogs +}) => { + const [isExpanded, setIsExpanded] = (0, import_react8.useState)(false); + const normalizedMessage = fullError ? toErrorMessage(fullError, false) : message_; + const details = isExpanded && fullError ? errorDetails(fullError) : null; + const isExpandable = !!fullError && (fullError.stack || fullError.message !== normalizedMessage); + const message = normalizedMessage + ""; + return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `void-rounded-lg void-border void-border-red-200 void-bg-red-50 void-p-4 void-overflow-auto`, children: [ + /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "void-flex void-items-start void-justify-between", children: [ + /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "void-flex void-gap-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CircleAlert, { className: "void-h-5 void-w-5 void-text-red-600 void-mt-0.5" }), + /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "void-flex-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "void-font-semibold void-text-red-800", children: "Error" }), + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "void-text-red-700 void-mt-1", children: message }) + ] }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "void-flex void-gap-2", children: [ + isExpandable && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( + "button", + { + className: "void-text-red-600 hover:void-text-red-800 void-p-1 void-rounded", + onClick: () => setIsExpanded(!isExpanded), + children: isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronUp, { className: "void-h-5 void-w-5" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronDown, { className: "void-h-5 void-w-5" }) + } + ), + showDismiss && onDismiss && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( + "button", + { + className: "void-text-red-600 hover:void-text-red-800 void-p-1 void-rounded", + onClick: onDismiss, + children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(X, { className: "void-h-5 void-w-5" }) + } + ) + ] }) + ] }), + (onRetry || onRollback || onOpenLogs) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "void-mt-3 void-flex void-gap-2 void-flex-wrap", children: [ + onRetry && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)( + "button", + { + className: "void-flex void-items-center void-gap-1 void-px-3 void-py-1.5 void-text-sm void-bg-red-600 void-text-white void-rounded hover:void-bg-red-700 void-transition-colors", + onClick: onRetry, + "aria-label": "Retry operation", + children: [ + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RefreshCw, { className: "void-h-4 void-w-4" }), + "Retry" + ] + } + ), + onRollback && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)( + "button", + { + className: "void-flex void-items-center void-gap-1 void-px-3 void-py-1.5 void-text-sm void-bg-red-500 void-text-white void-rounded hover:void-bg-red-600 void-transition-colors", + onClick: onRollback, + "aria-label": "Rollback changes", + children: [ + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RotateCcw, { className: "void-h-4 void-w-4" }), + "Rollback" + ] + } + ), + onOpenLogs && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)( + "button", + { + className: "void-flex void-items-center void-gap-1 void-px-3 void-py-1.5 void-text-sm void-border void-border-red-300 void-text-red-700 void-rounded hover:void-bg-red-50 void-transition-colors", + onClick: onOpenLogs, + "aria-label": "Open logs", + children: [ + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FileText, { className: "void-h-4 void-w-4" }), + "Open Logs" + ] + } + ) + ] }), + isExpanded && details && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "void-mt-4 void-space-y-3 void-border-t void-border-red-200 void-pt-3 void-overflow-auto", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "void-font-semibold void-text-red-800", children: "Technical Details: " }), + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { className: "void-text-red-700 void-text-xs", children: details }) + ] }) }) + ] }); +}; + +// src2/void-settings-tsx/ModelDropdown.tsx +var import_react9 = __toESM(require_react(), 1); +var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1); +var optionsEqual = (m1, m2) => { + if (m1.length !== m2.length) return false; + for (let i = 0; i < m1.length; i++) { + if (!modelSelectionsEqual(m1[i].selection, m2[i].selection)) return false; + } + return true; +}; +var ModelSelectBox = ({ options: options2, featureName, className }) => { + const accessor = useAccessor(); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + const selection = cortexideSettingsService.state.modelSelectionOfFeature[featureName]; + const selectedOption = selection ? cortexideSettingsService.state._modelOptions.find((v) => modelSelectionsEqual(v.selection, selection)) : options2[0]; + const onChangeOption = (0, import_react9.useCallback)((newOption) => { + cortexideSettingsService.setModelSelectionOfFeature(featureName, newOption.selection); + }, [cortexideSettingsService, featureName]); + return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( + VoidCustomDropdownBox, + { + options: options2, + selectedOption, + onChangeOption, + getOptionDisplayName: (option) => { + if (option.selection.providerName === "auto" && option.selection.modelName === "auto") { + return "Auto"; + } + return option.selection.modelName; + }, + getOptionDropdownName: (option) => { + if (option.selection.providerName === "auto" && option.selection.modelName === "auto") { + return "Auto"; + } + return option.selection.modelName; + }, + getOptionDropdownDetail: (option) => { + if (option.selection.providerName === "auto" && option.selection.modelName === "auto") { + return "Automatic model selection"; + } + return option.selection.providerName; + }, + getOptionsEqual: (a, b) => optionsEqual([a], [b]), + className, + matchInputWidth: false + } + ); +}; +var MemoizedModelDropdown = ({ featureName, className }) => { + const settingsState = useSettingsState(); + const oldOptionsRef = (0, import_react9.useRef)([]); + const [memoizedOptions, setMemoizedOptions] = (0, import_react9.useState)(oldOptionsRef.current); + const { filter, emptyMessage } = modelFilterOfFeatureName[featureName]; + (0, import_react9.useEffect)(() => { + const oldOptions = oldOptionsRef.current; + const allOptions = featureName === "Chat" ? settingsState._modelOptions : settingsState._modelOptions.filter((o) => !(o.selection.providerName === "auto" && o.selection.modelName === "auto")); + const newOptions = allOptions.filter((o) => filter(o.selection, { chatMode: settingsState.globalSettings.chatMode, overridesOfModel: settingsState.overridesOfModel })); + if (!optionsEqual(oldOptions, newOptions)) { + setMemoizedOptions(newOptions); + } + oldOptionsRef.current = newOptions; + }, [settingsState._modelOptions, filter, featureName]); + if (memoizedOptions.length === 0) { + return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WarningBox, { text: emptyMessage?.message || "No models available" }); + } + return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ModelSelectBox, { featureName, options: memoizedOptions, className }); +}; +var ModelDropdown = ({ featureName, className }) => { + const settingsState = useSettingsState(); + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); + const openSettings = () => { + commandService.executeCommand(CORTEXIDE_OPEN_SETTINGS_ACTION_ID); + }; + const { emptyMessage } = modelFilterOfFeatureName[featureName]; + const isDisabled = isFeatureNameDisabled$1(featureName, settingsState); + if (isDisabled) + return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WarningBox, { onClick: openSettings, text: emptyMessage && emptyMessage.priority === "always" ? emptyMessage.message : isDisabled === "needToEnableModel" ? "Enable a model" : isDisabled === "addModel" ? "Add a model" : isDisabled === "addProvider" || isDisabled === "notFilledIn" || isDisabled === "providerNotAutoDetected" ? "Provider required" : "Provider required" }); + return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MemoizedModelDropdown, { featureName, className }) }); +}; + +// src2/sidebar-tsx/SidebarThreadSelector.tsx +var import_react10 = __toESM(require_react(), 1); +var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1); +var numInitialThreads = 3; +var PastThreadsList = ({ className = "" }) => { + const [showAll, setShowAll] = (0, import_react10.useState)(false); + const [hoveredIdx, setHoveredIdx] = (0, import_react10.useState)(null); + const threadsState = useChatThreadsState(); + const { allThreads } = threadsState; + const streamState = useFullChatThreadsStreamState(); + const runningThreadIds = (0, import_react10.useMemo)(() => { + const result = {}; + for (const threadId in streamState) { + const isRunning = streamState[threadId]?.isRunning; + if (isRunning) { + result[threadId] = isRunning; + } + } + return result; + }, [streamState]); + if (!allThreads) { + return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "void-p-1", children: `Error accessing chat history.` }, "error"); + } + const sortedThreadIds = (0, import_react10.useMemo)(() => { + return Object.keys(allThreads ?? {}).sort((threadId1, threadId2) => (allThreads[threadId1]?.lastModified ?? 0) > (allThreads[threadId2]?.lastModified ?? 0) ? -1 : 1).filter((threadId) => (allThreads[threadId]?.messages.length ?? 0) !== 0); + }, [allThreads]); + const hasMoreThreads = sortedThreadIds.length > numInitialThreads; + const displayThreads = showAll ? sortedThreadIds : sortedThreadIds.slice(0, numInitialThreads); + return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `void-flex void-flex-col void-mb-2 void-gap-2 void-w-full void-text-nowrap void-text-void-fg-2 void-select-none void-relative ${className}`, children: [ + displayThreads.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, {}) : displayThreads.map((threadId, i) => { + const pastThread = allThreads[threadId]; + if (!pastThread) { + return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "void-p-1", children: `Error accessing chat history.` }, i); + } + return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( + PastThreadElement, + { + pastThread, + idx: i, + hoveredIdx, + setHoveredIdx, + isRunning: runningThreadIds[pastThread.id] + }, + pastThread.id + ); + }), + hasMoreThreads && !showAll && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)( + "div", + { + className: "void-text-void-fg-3 void-opacity-80 hover:void-opacity-100 hover:void-brightness-115 void-cursor-pointer void-p-1 void-text-xs", + onClick: () => setShowAll(true), + children: [ + "Show ", + sortedThreadIds.length - numInitialThreads, + " more..." + ] + } + ), + hasMoreThreads && showAll && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( + "div", + { + className: "void-text-void-fg-3 void-opacity-80 hover:void-opacity-100 hover:void-brightness-115 void-cursor-pointer void-p-1 void-text-xs", + onClick: () => setShowAll(false), + children: "Show less" + } + ) + ] }); +}; +var formatDate = (date) => { + const now = /* @__PURE__ */ new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const yesterday = new Date(today); + yesterday.setDate(yesterday.getDate() - 1); + if (date >= today) { + return "Today"; + } else if (date >= yesterday) { + return "Yesterday"; + } else { + return `${date.toLocaleString("default", { month: "short" })} ${date.getDate()}`; + } +}; +var DuplicateButton = ({ threadId }) => { + const accessor = useAccessor(); + const chatThreadsService = accessor.get("IChatThreadService"); + return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( + IconShell1, + { + Icon: Copy, + className: "void-size-[11px]", + onClick: () => { + chatThreadsService.duplicateThread(threadId); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "top", + "data-tooltip-content": "Duplicate thread" + } + ); +}; +var TrashButton = ({ threadId }) => { + const accessor = useAccessor(); + const chatThreadsService = accessor.get("IChatThreadService"); + const [isTrashPressed, setIsTrashPressed] = (0, import_react10.useState)(false); + return isTrashPressed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "void-flex void-flex-nowrap void-text-nowrap void-gap-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( + IconShell1, + { + Icon: X, + className: "void-size-[11px]", + onClick: () => { + setIsTrashPressed(false); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "top", + "data-tooltip-content": "Cancel" + } + ), + /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( + IconShell1, + { + Icon: Check, + className: "void-size-[11px]", + onClick: () => { + chatThreadsService.deleteThread(threadId); + setIsTrashPressed(false); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "top", + "data-tooltip-content": "Confirm" + } + ) + ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( + IconShell1, + { + Icon: Trash2, + className: "void-size-[11px]", + onClick: () => { + setIsTrashPressed(true); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "top", + "data-tooltip-content": "Delete thread" + } + ); +}; +var PastThreadElement = ({ + pastThread, + idx, + hoveredIdx, + setHoveredIdx, + isRunning +}) => { + const accessor = useAccessor(); + const chatThreadsService = accessor.get("IChatThreadService"); + let firstMsg = null; + const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role === "user"); + if (firstUserMsgIdx !== -1) { + const firsUsertMsgObj = pastThread.messages[firstUserMsgIdx]; + firstMsg = firsUsertMsgObj.role === "user" && firsUsertMsgObj.displayContent || ""; + } else { + firstMsg = '""'; + } + const numMessages = pastThread.messages.filter((msg) => msg.role === "assistant" || msg.role === "user").length; + const detailsHTML = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "void-inline-flex void-items-center void-gap-1 void-px-2 void-py-0.5 void-rounded-full void-bg-void-bg-2 void-text-[10px] void-tracking-wide void-uppercase void-text-void-fg-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [ + numMessages, + " msg" + ] }), + /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "void-opacity-80", children: formatDate(new Date(pastThread.lastModified)) }) + ] }); + return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( + "div", + { + className: ` void-group void-px-3 void-py-2 void-rounded-xl void-border void-border-void-border-3/70 void-bg-void-bg-1/40 hover:void-bg-void-bg-2/70 void-cursor-pointer void-text-sm void-text-void-fg-1 void-transition-all void-duration-150 void-ease-out void-shadow-[0_8px_20px_rgba(0,0,0,0.35)] hover:-void-translate-y-0.5 `, + onClick: () => { + chatThreadsService.openTab(pastThread.id); + }, + onMouseEnter: () => setHoveredIdx(idx), + onMouseLeave: () => setHoveredIdx(null), + children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "void-flex void-items-center void-justify-between void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "void-flex void-items-center void-gap-2 void-min-w-0 void-overflow-hidden void-text-void-fg-2", children: [ + isRunning === "LLM" || isRunning === "tool" || isRunning === "preparing" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LoaderCircle, { className: "void-animate-spin void-text-void-fg-1 void-flex-shrink-0 void-flex-grow-0", size: 14 }) : isRunning === "awaiting_user" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MessageCircleQuestion, { className: "void-text-void-fg-1 void-flex-shrink-0 void-flex-grow-0", size: 14 }) : null, + /* @__PURE__ */ (0, import_jsx_runtime7.jsx)( + "span", + { + className: "void-truncate void-overflow-hidden void-text-ellipsis void-text-void-fg-1", + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": numMessages + " messages", + "data-tooltip-place": "top", + children: firstMsg + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "void-flex void-items-center void-gap-x-1 void-opacity-80 void-text-void-fg-3", children: idx === hoveredIdx ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DuplicateButton, { threadId: pastThread.id }), + /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TrashButton, { threadId: pastThread.id }) + ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "void-opacity-90", children: detailsHTML }) }) }) + ] }) + }, + pastThread.id + ); +}; + +// src2/sidebar-tsx/ChatTabsBar.tsx +var import_react11 = __toESM(require_react(), 1); +var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1); +var getThreadTitle = (thread) => { + if (!thread) return "New Chat"; + const firstUserMsgIdx = thread.messages.findIndex((msg) => msg.role === "user"); + if (firstUserMsgIdx !== -1) { + const firstUserMsg = thread.messages[firstUserMsgIdx]; + if (firstUserMsg.role === "user" && firstUserMsg.displayContent) { + const title = firstUserMsg.displayContent; + return title.length > 30 ? title.substring(0, 30) + "..." : title; + } + } + return "New Chat"; +}; +var ChatTab = ({ threadId, isActive, onClick, onClose, isRunning }) => { + const threadsState = useChatThreadsState(); + const thread = threadsState.allThreads[threadId]; + const title = getThreadTitle(thread); + return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)( + "div", + { + className: ` void-group void-flex void-items-center void-gap-1.5 void-px-3 void-py-1.5 void-rounded-t-lg void-border-b-2 void-transition-all void-duration-150 void-cursor-pointer ${isActive ? "void-bg-void-bg-2 void-border-void-fg-1 void-text-void-fg-1" : "void-bg-void-bg-1/40 void-border-transparent void-text-void-fg-2 hover:void-bg-void-bg-1/60 hover:void-text-void-fg-1"} `, + onClick, + children: [ + isRunning === "LLM" || isRunning === "tool" || isRunning === "preparing" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LoaderCircle, { className: "void-animate-spin void-text-void-fg-1 void-flex-shrink-0", size: 12 }) : isRunning === "awaiting_user" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageCircleQuestion, { className: "void-text-void-fg-1 void-flex-shrink-0", size: 12 }) : null, + /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "void-text-xs void-truncate void-max-w-[120px]", title, children: title }), + /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( + IconShell1, + { + Icon: X, + className: "void-size-[11px] void-opacity-0 group-hover:void-opacity-100 void-transition-opacity void-flex-shrink-0", + onClick: (e) => { + e.stopPropagation(); + onClose(e); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "top", + "data-tooltip-content": "Close tab" + } + ) + ] + } + ); +}; +var ChatTabsBar = () => { + const accessor = useAccessor(); + const chatThreadsService = accessor.get("IChatThreadService"); + const threadsState = useChatThreadsState(); + const streamState = useFullChatThreadsStreamState(); + const { openTabs, currentThreadId } = threadsState; + const runningThreadIds = (0, import_react11.useMemo)(() => { + const result = {}; + for (const threadId in streamState) { + const isRunning = streamState[threadId]?.isRunning; + if (isRunning) { + result[threadId] = isRunning; + } + } + return result; + }, [streamState]); + const validTabs = (0, import_react11.useMemo)(() => { + return openTabs.filter((threadId) => threadsState.allThreads[threadId] !== void 0); + }, [openTabs, threadsState.allThreads]); + if (validTabs.length === 0) { + return null; + } + const handleTabClick = (threadId) => { + chatThreadsService.switchToTab(threadId); + }; + const handleTabClose = (threadId) => { + chatThreadsService.closeTab(threadId); + }; + const handleNewTab = () => { + chatThreadsService.openNewThread(); + }; + return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "void-flex void-items-end void-gap-1 void-px-2 void-pt-2 void-border-b void-border-void-border-3 void-bg-void-bg-1/30", children: [ + /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "void-flex void-items-end void-gap-1 void-overflow-x-auto void-flex-1 void-min-w-0", children: validTabs.map( + (threadId) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( + ChatTab, + { + threadId, + isActive: threadId === currentThreadId, + onClick: () => handleTabClick(threadId), + onClose: () => handleTabClose(threadId), + isRunning: runningThreadIds[threadId] + }, + threadId + ) + ) }), + /* @__PURE__ */ (0, import_jsx_runtime8.jsx)( + "button", + { + className: "void-px-2 void-py-1.5 void-rounded-t-lg void-bg-void-bg-1/40 hover:void-bg-void-bg-1/60 void-text-void-fg-2 hover:void-text-void-fg-1 void-transition-all void-duration-150 void-text-xs void-border-b-2 void-border-transparent hover:void-border-void-border-3 void-flex-shrink-0", + onClick: handleNewTab, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "top", + "data-tooltip-content": "New chat tab", + children: "+" + } + ) + ] }); +}; + +// src2/util/useImageAttachments.ts +var import_react12 = __toESM(require_react(), 1); + +// src2/util/imageUtils.ts +var ALLOWED_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif", "image/svg+xml"]; +var MAX_DIMENSION = 2048; +function toArrayBuffer(data) { + const buffer = new ArrayBuffer(data.byteLength); + new Uint8Array(buffer).set(data); + return buffer; +} +function formatFileSize(bytes) { + if (bytes === 0) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; +} +function validateImageFile(file) { + if (!ALLOWED_IMAGE_MIME_TYPES.includes(file.type)) { + return { + type: "mime_type", + message: `Unsupported image type: ${file.type}. Supported: PNG, JPEG, WebP, GIF, SVG.` + }; + } + if (file.size > 30 * 1024 * 1024) { + return { + type: "size", + message: `Image is too large: ${formatFileSize(file.size)}. Maximum: 30 MB.` + }; + } + return null; +} +async function processImage(file, onProgress) { + const validationError = validateImageFile(file); + if (validationError) { + throw new Error(validationError.message); + } + onProgress?.(0.1); + const mimeType = file.type; + if (mimeType === "image/svg+xml") { + onProgress?.(0.3); + return await processSvgImage(file, onProgress); + } + onProgress?.(0.2); + const arrayBuffer = await file.arrayBuffer(); + const uint8Array = new Uint8Array(arrayBuffer); + onProgress?.(0.4); + const img = await loadImageWithOrientation(uint8Array, onProgress); + let targetWidth = img.width; + let targetHeight = img.height; + const needsResize = img.width > MAX_DIMENSION || img.height > MAX_DIMENSION; + if (needsResize) { + const scaleFactor = MAX_DIMENSION / Math.max(img.width, img.height); + targetWidth = Math.round(img.width * scaleFactor); + targetHeight = Math.round(img.height * scaleFactor); + } + onProgress?.(0.6); + const canvas = document.createElement("canvas"); + canvas.width = targetWidth; + canvas.height = targetHeight; + const ctx = canvas.getContext("2d", { willReadFrequently: false }); + if (!ctx) { + throw new Error("Failed to get canvas context"); + } + if (img.orientation && img.orientation !== 1) { + applyOrientation(ctx, img.orientation, targetWidth, targetHeight); + } + onProgress?.(0.7); + ctx.drawImage(img.image, 0, 0, targetWidth, targetHeight); + const outputMimeType = mimeType === "image/png" ? "image/png" : "image/jpeg"; + const quality = determineQuality(mimeType, targetWidth, targetHeight); + onProgress?.(0.8); + return new Promise((resolve, reject) => { + canvas.toBlob( + (blob) => { + if (!blob) { + reject(new Error("Failed to create blob from canvas")); + return; + } + onProgress?.(0.9); + const reader = new FileReader(); + reader.onload = () => { + const data = new Uint8Array(reader.result); + onProgress?.(1); + resolve({ + data, + mimeType: outputMimeType, + width: targetWidth, + height: targetHeight, + filename: sanitizeFilename(file.name), + size: data.length, + originalSize: file.size + }); + }; + reader.onerror = () => reject(new Error("Failed to read blob")); + reader.readAsArrayBuffer(blob); + }, + outputMimeType, + quality + ); + }); +} +function parseExifOrientation(data) { + if (data.length < 4 || data[0] !== 255 || data[1] !== 216) { + return 1; + } + let offset3 = 2; + while (offset3 < data.length - 1) { + if (data[offset3] === 255 && data[offset3 + 1] === 225) { + if (offset3 + 6 < data.length) { + const exifMarker = String.fromCharCode( + data[offset3 + 4], + data[offset3 + 5], + data[offset3 + 6], + data[offset3 + 7] + ); + if (exifMarker === "Exif") { + const tiffOffset = offset3 + 10; + if (tiffOffset + 8 < data.length) { + const isIntel = data[tiffOffset] === 73 && data[tiffOffset + 1] === 73; + if (isIntel || data[tiffOffset] === 77 && data[tiffOffset + 1] === 77) { + let ifdOffsetValue = 0; + if (tiffOffset + 8 < data.length) { + if (isIntel) { + ifdOffsetValue = data[tiffOffset + 4] | data[tiffOffset + 5] << 8 | data[tiffOffset + 6] << 16 | data[tiffOffset + 7] << 24; + } else { + ifdOffsetValue = data[tiffOffset + 4] << 24 | data[tiffOffset + 5] << 16 | data[tiffOffset + 6] << 8 | data[tiffOffset + 7]; + } + if (ifdOffsetValue >= 0) { + const ifdOffset = tiffOffset + ifdOffsetValue; + if (ifdOffset < data.length && ifdOffset + 2 < data.length) { + let numEntries = 0; + if (isIntel) { + numEntries = data[ifdOffset] | data[ifdOffset + 1] << 8; + } else { + numEntries = data[ifdOffset] << 8 | data[ifdOffset + 1]; + } + let entryOffset = ifdOffset + 2; + for (let i = 0; i < numEntries && entryOffset + 12 < data.length; i++, entryOffset += 12) { + let tag2 = 0; + if (isIntel) { + tag2 = data[entryOffset] | data[entryOffset + 1] << 8; + } else { + tag2 = data[entryOffset] << 8 | data[entryOffset + 1]; + } + if (tag2 === 274) { + let orientation = 0; + if (isIntel) { + orientation = data[entryOffset + 8] | data[entryOffset + 9] << 8; + } else { + orientation = data[entryOffset + 8] << 8 | data[entryOffset + 9]; + } + if (orientation >= 1 && orientation <= 8) { + return orientation; + } + } + } + } + } + } + } + } + } + } + } + if (offset3 + 2 < data.length) { + const segmentLength = data[offset3 + 2] << 8 | data[offset3 + 3]; + offset3 += 2 + segmentLength; + } else { + break; + } + } + return 1; +} +async function loadImageWithOrientation(data, onProgress) { + return new Promise((resolve, reject) => { + onProgress?.(0.5); + const blob = new Blob([toArrayBuffer(data)]); + const url = URL.createObjectURL(blob); + const img = new Image(); + img.onload = () => { + URL.revokeObjectURL(url); + onProgress?.(0.6); + const orientation = parseExifOrientation(data); + resolve({ + image: img, + width: img.naturalWidth, + height: img.naturalHeight, + orientation + }); + }; + img.onerror = () => { + URL.revokeObjectURL(url); + reject(new Error("Failed to load image")); + }; + img.src = url; + }); +} +async function processSvgImage(file, onProgress) { + onProgress?.(0.3); + const svgText = await file.text(); + let sanitized = svgText.replace(//gi, "").replace(/]*>[\s\S]*?<\/style>/gi, (match) => { + if (/expression\s*\(|javascript:|@import|url\s*\(/i.test(match)) { + return ""; + } + return match; + }).replace(/on\w+\s*=\s*["'][^"']*["']/gi, "").replace(/javascript:/gi, "").replace(/]*href\s*=\s*["'][^"']*["'][^>]*>/gi, "").replace(/]*href\s*=\s*["'][^"']*["'][^>]*>/gi, "").replace(/]*xlink:href\s*=\s*["'][^"']*["'][^>]*>/gi, "").replace(/url\s*\(\s*["']?data:/gi, "url(#blocked)").replace(//gi, "").replace(//gi, "").replace(/]*>/gi, "").replace(/<\?xml-stylesheet[^>]*\?>/gi, "").replace(/]*>/gi, ""); + const originalLength = svgText.length; + const sanitizedLength = sanitized.length; + const removalRatio = (originalLength - sanitizedLength) / originalLength; + if (removalRatio > 0.3) { + console.warn("SVG sanitization removed >30% of content, using raster fallback for safety"); + } + onProgress?.(0.5); + return new Promise((resolve, reject) => { + const img = new Image(); + const blob = new Blob([sanitized], { type: "image/svg+xml" }); + const url = URL.createObjectURL(blob); + const loadTimeout = setTimeout(() => { + URL.revokeObjectURL(url); + reject(new Error("SVG loading timeout - file may be corrupted or malicious")); + }, 1e4); + img.onload = () => { + clearTimeout(loadTimeout); + URL.revokeObjectURL(url); + onProgress?.(0.7); + const canvas = document.createElement("canvas"); + canvas.width = img.naturalWidth || 800; + canvas.height = img.naturalHeight || 600; + const ctx = canvas.getContext("2d"); + if (!ctx) { + reject(new Error("Failed to get canvas context for SVG")); + return; + } + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + onProgress?.(0.8); + canvas.toBlob((blob2) => { + if (!blob2) { + reject(new Error("Failed to rasterize SVG")); + return; + } + onProgress?.(0.9); + const reader = new FileReader(); + reader.onload = () => { + const data = new Uint8Array(reader.result); + onProgress?.(1); + resolve({ + data, + mimeType: "image/png", + width: canvas.width, + height: canvas.height, + filename: sanitizeFilename(file.name.replace(/\.svg$/i, ".png")), + size: data.length, + originalSize: file.size + }); + }; + reader.onerror = () => reject(new Error("Failed to read rasterized SVG")); + reader.readAsArrayBuffer(blob2); + }, "image/png", 0.95); + }; + img.onerror = () => { + clearTimeout(loadTimeout); + URL.revokeObjectURL(url); + onProgress?.(0.7); + const canvas = document.createElement("canvas"); + canvas.width = 800; + canvas.height = 600; + const ctx = canvas.getContext("2d"); + if (ctx) { + ctx.fillStyle = "#f0f0f0"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "#666"; + ctx.font = "16px sans-serif"; + ctx.textAlign = "center"; + ctx.fillText("SVG preview unavailable", canvas.width / 2, canvas.height / 2); + canvas.toBlob((blob2) => { + if (blob2) { + const reader = new FileReader(); + reader.onload = () => { + const data = new Uint8Array(reader.result); + resolve({ + data, + mimeType: "image/png", + width: canvas.width, + height: canvas.height, + filename: sanitizeFilename(file.name.replace(/\.svg$/i, ".png")), + size: data.length, + originalSize: file.size + }); + }; + reader.readAsArrayBuffer(blob2); + } else { + reject(new Error("Failed to load SVG and create fallback")); + } + }, "image/png"); + } else { + reject(new Error("Failed to load SVG")); + } + }; + img.src = url; + }); +} +function applyOrientation(ctx, orientation, width, height) { + ctx.save(); + switch (orientation) { + case 2: + ctx.translate(width, 0); + ctx.scale(-1, 1); + break; + case 3: + ctx.translate(width, height); + ctx.rotate(Math.PI); + break; + case 4: + ctx.translate(0, height); + ctx.scale(1, -1); + break; + case 5: + ctx.translate(height, 0); + ctx.rotate(Math.PI / 2); + ctx.scale(-1, 1); + break; + case 6: + ctx.translate(height, 0); + ctx.rotate(Math.PI / 2); + break; + case 7: + ctx.translate(0, width); + ctx.rotate(-Math.PI / 2); + ctx.scale(-1, 1); + break; + case 8: + ctx.translate(0, width); + ctx.rotate(-Math.PI / 2); + break; + } + ctx.restore(); +} +function determineQuality(mimeType, width, height) { + if (mimeType === "image/png") { + return 1; + } + const pixels = width * height; + if (pixels > 2e6) { + return 0.85; + } else if (pixels > 1e6) { + return 0.9; + } else { + return 0.92; + } +} +function sanitizeFilename(filename) { + return filename.replace(/^.*[\/\\]/, "").replace(/\0/g, "").replace(/[<>:"|?*\x00-\x1f]/g, "_").substring(0, 255); +} +function createImageDataUrl(data, mimeType) { + const blob = new Blob([toArrayBuffer(data)], { type: mimeType }); + return URL.createObjectURL(blob); +} +function revokeImageDataUrl(url) { + URL.revokeObjectURL(url); +} + +// src2/util/useImageAttachments.ts +function useImageAttachments() { + const [attachments, setAttachments] = (0, import_react12.useState)([]); + const [focusedIndex, setFocusedIndex] = (0, import_react12.useState)(null); + const [validationError, setValidationError] = (0, import_react12.useState)(null); + const processingRef = (0, import_react12.useRef)(/* @__PURE__ */ new Set()); + const cancelRef = (0, import_react12.useRef)(/* @__PURE__ */ new Map()); + const originalFilesRef = (0, import_react12.useRef)(/* @__PURE__ */ new Map()); + const addImages = (0, import_react12.useCallback)(async (files) => { + setValidationError(null); + const validationErrors = []; + for (const file of files) { + const error2 = validateImageFile(file); + if (error2) { + if (error2.type === "size") { + validationErrors.push({ + ...error2, + message: `${file.name}: ${error2.message}` + }); + } else { + validationErrors.push({ + ...error2, + message: `${file.name}: ${error2.message}` + }); + } + } + } + if (validationErrors.length > 0) { + setValidationError(validationErrors[0]); + return; + } + if (attachments.length + files.length > 10) { + setValidationError({ + type: "count", + message: `Too many images: ${attachments.length + files.length}. Maximum: 10 images per message.` + }); + return; + } + for (const file of files) { + const id = `${Date.now()}-${Math.random().toString(36).substring(7)}`; + originalFilesRef.current.set(id, file); + const placeholder = { + id, + data: new Uint8Array(0), + mimeType: file.type, + filename: file.name, + width: 0, + height: 0, + size: 0, + uploadStatus: "pending" + }; + setAttachments((prev) => [...prev, placeholder]); + processingRef.current.add(id); + let cancelled = false; + const cancelFn = () => { + cancelled = true; + processingRef.current.delete(id); + cancelRef.current.delete(id); + originalFilesRef.current.delete(id); + setAttachments((prev) => prev.filter((att) => att.id !== id)); + }; + cancelRef.current.set(id, cancelFn); + const updateProgress = (progress) => { + if (cancelled) return; + setAttachments((prev) => prev.map( + (att) => att.id === id ? { ...att, uploadStatus: "uploading", uploadProgress: progress } : att + )); + }; + try { + updateProgress(0.1); + const processed = await processImage(file, (stageProgress) => { + const progress = 0.1 + stageProgress * 0.8; + updateProgress(progress); + }); + if (cancelled) return; + setAttachments((prev) => prev.map( + (att) => att.id === id ? { + ...att, + data: processed.data, + mimeType: processed.mimeType, + filename: processed.filename, + width: processed.width, + height: processed.height, + size: processed.size, + uploadStatus: "success", + uploadProgress: 1 + } : att + )); + setAttachments((prev) => { + const successful = prev.filter((a) => a.uploadStatus === "success"); + const totalSize = successful.reduce((sum, img) => sum + img.size, 0); + const maxTotalSize = 20 * 1024 * 1024; + if (successful.length > 10) { + const error2 = { + type: "count", + message: `Too many images: ${successful.length}. Maximum: 10.` + }; + setValidationError(error2); + return prev.map( + (att) => att.id === id ? { ...att, uploadStatus: "failed", error: error2.message, uploadProgress: void 0 } : att + ); + } + if (totalSize > maxTotalSize) { + const error2 = { + type: "size", + message: `Total image size too large: ${formatFileSize(totalSize)}. Maximum: ${formatFileSize(maxTotalSize)}.` + }; + setValidationError(error2); + return prev.map( + (att) => att.id === id ? { ...att, uploadStatus: "failed", error: error2.message, uploadProgress: void 0 } : att + ); + } + return prev; + }); + cancelRef.current.delete(id); + } catch (error2) { + if (cancelled) return; + const errorMessage = error2 instanceof Error ? error2.message : "Failed to process image"; + setAttachments((prev) => prev.map( + (att) => att.id === id ? { + ...att, + uploadStatus: "failed", + error: errorMessage, + uploadProgress: void 0 + } : att + )); + setValidationError({ + type: "corrupt", + message: errorMessage + }); + } finally { + if (!cancelled) { + processingRef.current.delete(id); + cancelRef.current.delete(id); + } + } + } + }, [attachments.length]); + const removeImage = (0, import_react12.useCallback)((id) => { + const cancelFn = cancelRef.current.get(id); + if (cancelFn) { + cancelFn(); + return; + } + setAttachments((prev) => prev.filter((att) => att.id !== id)); + originalFilesRef.current.delete(id); + setValidationError(null); + if (focusedIndex !== null && focusedIndex >= attachments.length - 1) { + setFocusedIndex(Math.max(0, attachments.length - 2)); + } + }, [attachments.length, focusedIndex]); + const cancelImage = (0, import_react12.useCallback)((id) => { + const cancelFn = cancelRef.current.get(id); + if (cancelFn) { + cancelFn(); + } + }, []); + const retryImage = (0, import_react12.useCallback)(async (id) => { + const attachment = attachments.find((att) => att.id === id); + if (!attachment) return; + const originalFile = originalFilesRef.current.get(id); + if (!originalFile) { + setAttachments((prev) => prev.map( + (att) => att.id === id ? { + ...att, + uploadStatus: "failed", + error: "Cannot retry: original file not available" + } : att + )); + return; + } + setAttachments((prev) => prev.map( + (att) => att.id === id ? { ...att, uploadStatus: "pending", error: void 0, uploadProgress: void 0 } : att + )); + processingRef.current.add(id); + let cancelled = false; + const cancelFn = () => { + cancelled = true; + processingRef.current.delete(id); + cancelRef.current.delete(id); + }; + cancelRef.current.set(id, cancelFn); + const updateProgress = (progress) => { + if (cancelled) return; + setAttachments((prev) => prev.map( + (att) => att.id === id ? { ...att, uploadStatus: "uploading", uploadProgress: progress } : att + )); + }; + try { + updateProgress(0.1); + const processed = await processImage(originalFile, (stageProgress) => { + const progress = 0.1 + stageProgress * 0.8; + updateProgress(progress); + }); + if (cancelled) return; + setAttachments((prev) => prev.map( + (att) => att.id === id ? { + ...att, + data: processed.data, + mimeType: processed.mimeType, + filename: processed.filename, + width: processed.width, + height: processed.height, + size: processed.size, + uploadStatus: "success", + uploadProgress: 1 + } : att + )); + } catch (error2) { + if (cancelled) return; + const errorMessage = error2 instanceof Error ? error2.message : "Failed to process image"; + setAttachments((prev) => prev.map( + (att) => att.id === id ? { + ...att, + uploadStatus: "failed", + error: errorMessage, + uploadProgress: void 0 + } : att + )); + } finally { + if (!cancelled) { + processingRef.current.delete(id); + cancelRef.current.delete(id); + } + } + }, [attachments]); + const clearAll = (0, import_react12.useCallback)(() => { + cancelRef.current.forEach((cancelFn) => cancelFn()); + cancelRef.current.clear(); + processingRef.current.clear(); + originalFilesRef.current.clear(); + setAttachments([]); + setValidationError(null); + setFocusedIndex(null); + }, []); + return { + attachments, + addImages, + removeImage, + retryImage, + cancelImage, + clearAll, + focusedIndex, + setFocusedIndex, + validationError + }; +} + +// src2/util/usePDFAttachments.ts +var import_react13 = __toESM(require_react(), 1); +var MAX_PDF_SIZE = 50 * 1024 * 1024; +var MAX_PDFS = 5; +function usePDFAttachments() { + const [attachments, setAttachments] = (0, import_react13.useState)([]); + const [focusedIndex, setFocusedIndex] = (0, import_react13.useState)(null); + const [validationError, setValidationError] = (0, import_react13.useState)(null); + const processingRef = (0, import_react13.useRef)(/* @__PURE__ */ new Set()); + const cancelRef = (0, import_react13.useRef)(/* @__PURE__ */ new Map()); + const originalFilesRef = (0, import_react13.useRef)(/* @__PURE__ */ new Map()); + const pdfServiceRef = (0, import_react13.useRef)(null); + const getPDFServiceInstance = (0, import_react13.useCallback)(async () => { + if (!pdfServiceRef.current) { + pdfServiceRef.current = getPDFService(); + } + return pdfServiceRef.current; + }, []); + const addPDFs = (0, import_react13.useCallback)(async (files) => { + setValidationError(null); + for (const file of files) { + if (file.type !== "application/pdf") { + setValidationError(`${file.name} is not a PDF file.`); + return; + } + if (file.size > MAX_PDF_SIZE) { + setValidationError(`${file.name} is too large (${(file.size / 1024 / 1024).toFixed(1)}MB). Maximum: ${MAX_PDF_SIZE / 1024 / 1024}MB.`); + return; + } + } + if (attachments.length + files.length > MAX_PDFS) { + setValidationError(`Too many PDFs: ${attachments.length + files.length}. Maximum: ${MAX_PDFS} PDFs per message.`); + return; + } + const fileIds = files.map(() => `${Date.now()}-${Math.random().toString(36).substring(7)}`); + const placeholders = files.map((file, index3) => ({ + id: fileIds[index3], + data: new Uint8Array(0), + filename: file.name, + size: file.size, + uploadStatus: "pending" + })); + setAttachments((prev) => [...prev, ...placeholders]); + files.forEach((file, index3) => { + const id = fileIds[index3]; + originalFilesRef.current.set(id, file); + processingRef.current.add(id); + const cancelFn = () => { + processingRef.current.delete(id); + cancelRef.current.delete(id); + originalFilesRef.current.delete(id); + setAttachments((prev) => prev.filter((att) => att.id !== id)); + }; + cancelRef.current.set(id, cancelFn); + }); + const pdfService = await getPDFServiceInstance(); + const processingPromises = files.map(async (file, index3) => { + const id = fileIds[index3]; + let cancelled = false; + const checkCancelled = () => { + if (!processingRef.current.has(id)) { + cancelled = true; + } + return cancelled; + }; + const updateProgress = (progress, status = "uploading") => { + if (checkCancelled()) return; + setAttachments((prev) => prev.map( + (att) => att.id === id ? { ...att, uploadStatus: status, uploadProgress: progress } : att + )); + }; + try { + updateProgress(0.1, "uploading"); + const arrayBuffer = await file.arrayBuffer(); + if (checkCancelled()) return; + updateProgress(0.3, "uploading"); + const data = new Uint8Array(arrayBuffer); + if (checkCancelled()) return; + updateProgress(0.5, "processing"); + const previewPageCount = 3; + const previewPageNumbers = Array.from({ length: previewPageCount }, (_, i) => i + 1); + const pdfDocWithPreviews = await pdfService.extractPDFWithPreviews(data, { + extractImages: false, + extractMetadata: true, + previewPages: previewPageNumbers, + // Only generate previews for first 3 pages + previewMaxWidth: 200, + previewMaxHeight: 300 + }); + if (checkCancelled()) return; + updateProgress(0.9, "processing"); + const selectedPages = Array.from({ length: pdfDocWithPreviews.pageCount }, (_, i) => i + 1); + const extractedText = pdfDocWithPreviews.pages.map((p) => `[Page ${p.pageNumber}] +${p.text}`).join("\n\n"); + if (checkCancelled()) return; + setAttachments((prev) => prev.map( + (att) => att.id === id ? { + ...att, + data, + pageCount: pdfDocWithPreviews.pageCount, + selectedPages, + extractedText, + pagePreviews: pdfDocWithPreviews.pagePreviews || [], + uploadStatus: "success", + uploadProgress: 1 + } : att + )); + processingRef.current.delete(id); + cancelRef.current.delete(id); + } catch (error2) { + if (checkCancelled()) return; + console.error("Error processing PDF:", error2); + setAttachments((prev) => prev.map( + (att) => att.id === id ? { + ...att, + uploadStatus: "failed", + error: error2.message || "Failed to process PDF" + } : att + )); + processingRef.current.delete(id); + cancelRef.current.delete(id); + } + }); + await Promise.allSettled(processingPromises); + }, [attachments.length, getPDFServiceInstance]); + const removePDF = (0, import_react13.useCallback)((id) => { + cancelRef.current.get(id)?.(); + setAttachments((prev) => prev.filter((att) => att.id !== id)); + originalFilesRef.current.delete(id); + }, []); + const retryPDF = (0, import_react13.useCallback)(async (id) => { + const originalFile = originalFilesRef.current.get(id); + if (!originalFile) return; + removePDF(id); + await addPDFs([originalFile]); + }, [addPDFs, removePDF]); + const cancelPDF = (0, import_react13.useCallback)((id) => { + cancelRef.current.get(id)?.(); + }, []); + const clearAll = (0, import_react13.useCallback)(() => { + attachments.forEach((att) => cancelRef.current.get(att.id)?.()); + setAttachments([]); + processingRef.current.clear(); + cancelRef.current.clear(); + originalFilesRef.current.clear(); + setValidationError(null); + }, [attachments]); + const updateSelectedPages = (0, import_react13.useCallback)((id, pages) => { + setAttachments((prev) => prev.map( + (att) => att.id === id ? { ...att, selectedPages: pages } : att + )); + }, []); + return { + attachments, + addPDFs, + removePDF, + retryPDF, + cancelPDF, + clearAll, + updateSelectedPages, + focusedIndex, + setFocusedIndex, + validationError + }; +} + +// src2/util/PDFAttachmentList.tsx +var import_react14 = __toESM(require_react(), 1); +var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1); +var formatFileSize2 = (bytes) => { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; +}; +var PDFAttachmentList = ({ + attachments, + onRemove, + onRetry, + onCancel, + focusedIndex, + onFocusChange +}) => { + const handleKeyDown = (0, import_react14.useCallback)((e, index3) => { + if (e.key === "ArrowLeft" && index3 > 0) { + e.preventDefault(); + onFocusChange(index3 - 1); + } else if (e.key === "ArrowRight" && index3 < attachments.length - 1) { + e.preventDefault(); + onFocusChange(index3 + 1); + } + }, [attachments.length, onFocusChange]); + if (attachments.length === 0) { + return null; + } + return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( + "div", + { + className: "void-flex void-flex-wrap void-gap-2 void-p-2 void-max-h-[300px] void-overflow-y-auto", + role: "list", + "aria-label": `${attachments.length} PDF attachment${attachments.length !== 1 ? "s" : ""}`, + children: attachments.map((attachment, index3) => { + const isUploading = attachment.uploadStatus === "uploading" || attachment.uploadStatus === "processing"; + const isFailed = attachment.uploadStatus === "failed"; + attachment.uploadStatus === "success" || !attachment.uploadStatus; + const focused = focusedIndex === index3; + return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)( + "div", + { + role: "listitem", + tabIndex: 0, + onKeyDown: (e) => { + handleKeyDown(e, index3); + if (e.key === "Delete" || e.key === "Backspace") { + e.preventDefault(); + onRemove(attachment.id); + } else if (e.key === "Enter" && isFailed && onRetry) { + e.preventDefault(); + onRetry(attachment.id); + } + }, + onFocus: () => onFocusChange(index3), + className: ` void-relative void-group void-flex void-flex-col void-w-[200px] void-min-h-[120px] void-rounded-md void-border void-border-void-border-3 void-bg-void-bg-2-alt void-overflow-hidden void-cursor-pointer void-transition-all void-duration-200 ${focused ? "void-ring-2 void-ring-blue-500 void-border-blue-500" : "hover:void-border-void-border-1"} ${isFailed ? "void-border-red-500" : ""} `, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "void-relative void-flex-1 void-w-full void-overflow-hidden void-bg-void-bg-1 void-flex void-items-center void-justify-center", children: [ + attachment.pagePreviews && attachment.pagePreviews.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( + "img", + { + src: attachment.pagePreviews[0], + alt: `Page 1 of ${attachment.filename}`, + className: "void-w-full void-h-full void-object-contain", + loading: "lazy" + } + ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FileText, { className: "void-w-12 void-h-12 void-text-void-fg-3" }), + isUploading && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "void-absolute void-inset-0 void-bg-black/50 void-flex void-items-center void-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "void-flex void-flex-col void-items-center void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LoaderCircle, { className: "void-w-5 void-h-5 void-text-white void-animate-spin" }), + attachment.uploadProgress !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "void-text-xs void-text-white", children: [ + Math.round(attachment.uploadProgress * 100), + "%" + ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "void-text-xs void-text-white", children: "Processing..." }), + onCancel && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( + "button", + { + type: "button", + onClick: (e) => { + e.stopPropagation(); + onCancel(attachment.id); + }, + className: "void-text-xs void-text-white/80 hover:void-text-white void-underline void-mt-1", + "aria-label": "Cancel processing", + children: "Cancel" + } + ) + ] }) }), + isFailed && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "void-absolute void-inset-0 void-bg-red-500/20 void-flex void-items-center void-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CircleAlert, { className: "void-w-5 void-h-5 void-text-red-500" }) }), + /* @__PURE__ */ (0, import_jsx_runtime9.jsx)( + "button", + { + type: "button", + onClick: (e) => { + e.stopPropagation(); + onRemove(attachment.id); + }, + "aria-label": `Remove ${attachment.filename}`, + className: "void-absolute void-top-1 void-right-1 void-p-1 void-rounded-md void-bg-black/60 hover:void-bg-black/80 void-text-white void-transition-opacity void-z-10 void-opacity-0 group-hover:void-opacity-100", + children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(X, { size: 14 }) + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "void-px-2 void-py-1.5 void-bg-void-bg-2-alt void-border-t void-border-void-border-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "void-text-xs void-font-medium void-text-void-fg-1 void-truncate", title: attachment.filename, children: attachment.filename }), + /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "void-flex void-items-center void-justify-between void-mt-0.5", children: [ + /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "void-text-[10px] void-text-void-fg-3", children: attachment.pageCount ? `${attachment.pageCount} page${attachment.pageCount !== 1 ? "s" : ""}` : formatFileSize2(attachment.size) }), + isFailed && attachment.error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "void-text-[10px] void-text-red-500 void-truncate void-max-w-[120px]", title: attachment.error, children: attachment.error }) + ] }) + ] }) + ] + }, + attachment.id + ); + }) + } + ); +}; + +// src2/util/ImageAttachmentList.tsx +var import_react16 = __toESM(require_react(), 1); + +// src2/util/ImageAttachmentChip.tsx +var import_react15 = __toESM(require_react(), 1); +var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1); +var ImageAttachmentChip = ({ + attachment, + onRemove, + onRetry, + onCancel, + index: index3, + focused, + onFocus +}) => { + const [previewUrl, setPreviewUrl] = (0, import_react15.useState)(null); + const chipRef = (0, import_react15.useRef)(null); + (0, import_react15.useEffect)(() => { + const url = createImageDataUrl(attachment.data, attachment.mimeType); + setPreviewUrl(url); + return () => { + if (url) { + revokeImageDataUrl(url); + } + }; + }, [attachment.data, attachment.mimeType]); + (0, import_react15.useEffect)(() => { + if (focused && chipRef.current) { + chipRef.current.focus(); + } + }, [focused]); + const handleKeyDown = (e) => { + if (e.key === "Delete" || e.key === "Backspace") { + e.preventDefault(); + onRemove(); + } else if (e.key === "Enter" && attachment.uploadStatus === "failed" && onRetry) { + e.preventDefault(); + onRetry(); + } + }; + const isUploading = attachment.uploadStatus === "uploading"; + const isFailed = attachment.uploadStatus === "failed"; + attachment.uploadStatus === "success" || !attachment.uploadStatus; + return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)( + "div", + { + ref: chipRef, + role: "button", + tabIndex: 0, + "aria-label": `Image attachment: ${attachment.filename}, ${formatFileSize(attachment.size)}. ${isUploading ? "Uploading" : isFailed ? "Failed" : "Ready"}`, + onFocus, + onKeyDown: handleKeyDown, + className: ` void-relative void-group void-flex void-flex-col void-w-[160px] void-h-[120px] void-rounded-md void-border void-border-void-border-3 void-bg-void-bg-2-alt void-overflow-hidden void-cursor-pointer void-transition-all void-duration-200 ${focused ? "void-ring-2 void-ring-blue-500 void-border-blue-500" : "hover:void-border-void-border-1"} ${isFailed ? "void-border-red-500" : ""} `, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "void-relative void-flex-1 void-w-full void-overflow-hidden void-bg-void-bg-1", children: [ + previewUrl ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)( + "img", + { + src: previewUrl, + alt: attachment.filename, + className: "void-w-full void-h-full void-object-cover", + loading: "lazy" + } + ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "void-w-full void-h-full void-flex void-items-center void-justify-center void-text-void-fg-3", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(LoaderCircle, { className: "void-w-6 void-h-6 void-animate-spin" }) }), + isUploading && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "void-absolute void-inset-0 void-bg-black/50 void-flex void-items-center void-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "void-flex void-flex-col void-items-center void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(LoaderCircle, { className: "void-w-5 void-h-5 void-text-white void-animate-spin" }), + attachment.uploadProgress !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "void-text-xs void-text-white", children: [ + Math.round(attachment.uploadProgress * 100), + "%" + ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "void-text-xs void-text-white", children: "Processing..." }), + onCancel && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)( + "button", + { + type: "button", + onClick: (e) => { + e.stopPropagation(); + onCancel(); + }, + className: "void-text-xs void-text-white/80 hover:void-text-white void-underline void-mt-1", + "aria-label": "Cancel upload", + children: "Cancel" + } + ) + ] }) }), + isFailed && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "void-absolute void-inset-0 void-bg-red-500/20 void-flex void-items-center void-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CircleAlert, { className: "void-w-5 void-h-5 void-text-red-500" }) }), + /* @__PURE__ */ (0, import_jsx_runtime10.jsx)( + "button", + { + type: "button", + onClick: (e) => { + e.stopPropagation(); + onRemove(); + }, + "aria-label": `Remove ${attachment.filename}`, + className: "void-absolute void-top-1 void-right-1 void-p-1 void-rounded-md void-bg-black/60 hover:void-bg-black/80 void-text-white void-transition-opacity void-z-10", + onMouseEnter: (e) => { + e.currentTarget.style.opacity = "1"; + }, + onMouseLeave: (e) => { + if (!isFailed && !isUploading) { + e.currentTarget.style.opacity = "0.7"; + } + }, + style: { opacity: isFailed || isUploading ? 1 : 0.7 }, + children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(X, { size: 14 }) + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "void-px-2 void-py-1 void-bg-void-bg-2-alt void-border-t void-border-void-border-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "void-text-xs void-text-void-fg-1 void-truncate", title: attachment.filename, children: attachment.filename }), + /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "void-text-xs void-text-void-fg-3", children: formatFileSize(attachment.size) }) + ] }), + isFailed && attachment.error && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "void-px-2 void-py-1 void-bg-red-500/10 void-border-t void-border-red-500", children: [ + /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "void-text-xs void-text-red-500 void-truncate", title: attachment.error, children: attachment.error }), + onRetry && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)( + "button", + { + type: "button", + onClick: (e) => { + e.stopPropagation(); + onRetry(); + }, + className: "void-text-xs void-text-blue-500 hover:void-text-blue-400 void-mt-1", + children: "Retry" + } + ) + ] }), + isUploading && attachment.uploadProgress !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "void-absolute void-bottom-0 void-left-0 void-right-0 void-h-1 void-bg-void-bg-1", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)( + "div", + { + className: "void-h-full void-bg-blue-500 void-transition-all void-duration-300", + style: { width: `${attachment.uploadProgress * 100}%` } + } + ) }) + ] + } + ); +}; + +// src2/util/ImageAttachmentList.tsx +var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1); +var ImageAttachmentList = ({ + attachments, + onRemove, + onRetry, + onCancel, + focusedIndex, + onFocusChange +}) => { + const handleKeyDown = (0, import_react16.useCallback)((e, index3) => { + if (e.key === "ArrowLeft" && index3 > 0) { + e.preventDefault(); + onFocusChange(index3 - 1); + } else if (e.key === "ArrowRight" && index3 < attachments.length - 1) { + e.preventDefault(); + onFocusChange(index3 + 1); + } + }, [attachments.length, onFocusChange]); + if (attachments.length === 0) { + return null; + } + return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)( + "div", + { + className: "void-flex void-flex-wrap void-gap-2 void-p-2 void-max-h-[300px] void-overflow-y-auto", + role: "list", + "aria-label": `${attachments.length} image attachment${attachments.length !== 1 ? "s" : ""}`, + children: attachments.map( + (attachment, index3) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)( + "div", + { + role: "listitem", + onKeyDown: (e) => handleKeyDown(e, index3), + children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)( + ImageAttachmentChip, + { + attachment, + onRemove: () => onRemove(attachment.id), + onRetry: onRetry ? () => onRetry(attachment.id) : void 0, + onCancel: onCancel ? () => onCancel(attachment.id) : void 0, + index: index3, + focused: focusedIndex === index3, + onFocus: () => onFocusChange(index3) + } + ) + }, + attachment.id + ) + ) + } + ); +}; + +// src2/util/ImageMessageRenderer.tsx +var import_react18 = __toESM(require_react(), 1); + +// src2/util/ImageLightbox.tsx +var import_react17 = __toESM(require_react(), 1); +var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1); +var ImageLightbox = ({ + images, + initialIndex, + previewUrls, + onClose, + onNavigate +}) => { + const [currentIndex, setCurrentIndex] = (0, import_react17.useState)(initialIndex); + const [scale, setScale] = (0, import_react17.useState)(1); + const [position, setPosition] = (0, import_react17.useState)({ x: 0, y: 0 }); + const [isDragging, setIsDragging] = (0, import_react17.useState)(false); + const [dragStart, setDragStart] = (0, import_react17.useState)({ x: 0, y: 0 }); + const [lastTouchDistance, setLastTouchDistance] = (0, import_react17.useState)(null); + const [lastTouchCenter, setLastTouchCenter] = (0, import_react17.useState)(null); + const imageRef = (0, import_react17.useRef)(null); + const containerRef = (0, import_react17.useRef)(null); + const previousFocusRef = (0, import_react17.useRef)(null); + const currentImage = images[currentIndex]; + const currentUrl = currentImage ? previewUrls.get(currentImage.id) : null; + (0, import_react17.useEffect)(() => { + setCurrentIndex(initialIndex); + setScale(1); + setPosition({ x: 0, y: 0 }); + }, [initialIndex]); + (0, import_react17.useEffect)(() => { + const handleKeyDown = (e) => { + if (e.key === "Escape") { + onClose(); + } else if (e.key === "ArrowLeft") { + e.preventDefault(); + onNavigate("prev"); + setCurrentIndex((prev) => Math.max(0, prev - 1)); + } else if (e.key === "ArrowRight") { + e.preventDefault(); + onNavigate("next"); + setCurrentIndex((prev) => Math.min(images.length - 1, prev + 1)); + } + }; + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [images.length, onClose, onNavigate]); + (0, import_react17.useEffect)(() => { + if (containerRef.current) { + previousFocusRef.current = document.activeElement; + containerRef.current.focus(); + } + return () => { + if (previousFocusRef.current) { + previousFocusRef.current.focus(); + } + }; + }, []); + const handleWheel = (0, import_react17.useCallback)((e) => { + if (e.ctrlKey || e.metaKey) { + e.preventDefault(); + const delta = e.deltaY > 0 ? 0.9 : 1.1; + setScale((prev) => Math.max(0.5, Math.min(5, prev * delta))); + } + }, []); + const handleMouseDown = (0, import_react17.useCallback)((e) => { + if (e.button === 0 && scale > 1) { + setIsDragging(true); + setDragStart({ x: e.clientX - position.x, y: e.clientY - position.y }); + } + }, [scale, position]); + const handleMouseMove = (0, import_react17.useCallback)((e) => { + if (isDragging) { + setPosition({ + x: e.clientX - dragStart.x, + y: e.clientY - dragStart.y + }); + } + }, [isDragging, dragStart]); + const handleMouseUp = (0, import_react17.useCallback)(() => { + setIsDragging(false); + }, []); + const handleDoubleClick = (0, import_react17.useCallback)(() => { + if (scale > 1) { + setScale(1); + setPosition({ x: 0, y: 0 }); + } else { + setScale(2); + } + }, [scale]); + const handlePrev = (0, import_react17.useCallback)(() => { + if (currentIndex > 0) { + setCurrentIndex(currentIndex - 1); + setScale(1); + setPosition({ x: 0, y: 0 }); + } + }, [currentIndex]); + const handleNext = (0, import_react17.useCallback)(() => { + if (currentIndex < images.length - 1) { + setCurrentIndex(currentIndex + 1); + setScale(1); + setPosition({ x: 0, y: 0 }); + } + }, [currentIndex, images.length]); + const handleTouchStart = (0, import_react17.useCallback)((e) => { + if (e.touches.length === 2) { + const touch1 = e.touches[0]; + const touch2 = e.touches[1]; + const distance = Math.hypot( + touch2.clientX - touch1.clientX, + touch2.clientY - touch1.clientY + ); + setLastTouchDistance(distance); + setLastTouchCenter({ + x: (touch1.clientX + touch2.clientX) / 2, + y: (touch1.clientY + touch2.clientY) / 2 + }); + } else if (e.touches.length === 1 && scale > 1) { + const touch = e.touches[0]; + setIsDragging(true); + setDragStart({ x: touch.clientX - position.x, y: touch.clientY - position.y }); + } + }, [scale, position]); + const handleTouchMove = (0, import_react17.useCallback)((e) => { + e.preventDefault(); + if (e.touches.length === 2 && lastTouchDistance !== null && lastTouchCenter) { + const touch1 = e.touches[0]; + const touch2 = e.touches[1]; + const distance = Math.hypot( + touch2.clientX - touch1.clientX, + touch2.clientY - touch1.clientY + ); + const scaleFactor = distance / lastTouchDistance; + setScale((prev) => Math.max(0.5, Math.min(5, prev * scaleFactor))); + setLastTouchDistance(distance); + const newCenter = { + x: (touch1.clientX + touch2.clientX) / 2, + y: (touch1.clientY + touch2.clientY) / 2 + }; + const centerDelta = { + x: newCenter.x - lastTouchCenter.x, + y: newCenter.y - lastTouchCenter.y + }; + setPosition((prev) => ({ + x: prev.x + centerDelta.x, + y: prev.y + centerDelta.y + })); + setLastTouchCenter(newCenter); + } else if (e.touches.length === 1 && isDragging) { + const touch = e.touches[0]; + setPosition({ + x: touch.clientX - dragStart.x, + y: touch.clientY - dragStart.y + }); + } + }, [lastTouchDistance, lastTouchCenter, isDragging, dragStart]); + const handleTouchEnd = (0, import_react17.useCallback)(() => { + setLastTouchDistance(null); + setLastTouchCenter(null); + setIsDragging(false); + }, []); + if (!currentImage || !currentUrl) { + return null; + } + return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)( + "div", + { + ref: containerRef, + role: "dialog", + "aria-modal": "true", + "aria-label": `Image ${currentIndex + 1} of ${images.length}: ${currentImage.filename}`, + tabIndex: -1, + className: "void-fixed void-inset-0 void-z-[9999] void-bg-black/90 void-flex void-items-center void-justify-center", + onClick: (e) => { + if (e.target === e.currentTarget) { + onClose(); + } + }, + onKeyDown: (e) => { + if (e.key === "Escape") { + onClose(); + } + }, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime12.jsx)( + "button", + { + type: "button", + onClick: onClose, + className: "void-absolute void-top-4 void-right-4 void-z-10 void-p-2 void-rounded-full void-bg-black/60 hover:void-bg-black/80 void-text-white void-transition-colors", + "aria-label": "Close lightbox", + children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(X, { size: 24 }) + } + ), + currentIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)( + "button", + { + type: "button", + onClick: handlePrev, + className: "void-absolute void-left-4 void-top-1/2 -void-translate-y-1/2 void-z-10 void-p-2 void-rounded-full void-bg-black/60 hover:void-bg-black/80 void-text-white void-transition-colors", + "aria-label": "Previous image", + children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ChevronLeft, { size: 24 }) + } + ), + currentIndex < images.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)( + "button", + { + type: "button", + onClick: handleNext, + className: "void-absolute void-right-4 void-top-1/2 -void-translate-y-1/2 void-z-10 void-p-2 void-rounded-full void-bg-black/60 hover:void-bg-black/80 void-text-white void-transition-colors", + "aria-label": "Next image", + children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ChevronRight, { size: 24 }) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime12.jsx)( + "div", + { + className: "void-relative void-w-full void-h-full void-flex void-items-center void-justify-center void-overflow-hidden void-touch-none", + onWheel: handleWheel, + onMouseDown: handleMouseDown, + onMouseMove: handleMouseMove, + onMouseUp: handleMouseUp, + onMouseLeave: handleMouseUp, + onTouchStart: handleTouchStart, + onTouchMove: handleTouchMove, + onTouchEnd: handleTouchEnd, + children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)( + "img", + { + ref: imageRef, + src: currentUrl, + alt: currentImage.filename || `Image ${currentIndex + 1}`, + onDoubleClick: handleDoubleClick, + className: "void-max-w-full void-max-h-full void-object-contain void-transition-transform void-duration-200", + style: { + transform: `scale(${scale}) translate(${position.x}px, ${position.y}px)`, + cursor: scale > 1 ? isDragging ? "grabbing" : "grab" : "zoom-in" + }, + draggable: false + } + ) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "void-absolute void-bottom-4 void-left-1/2 -void-translate-x-1/2 void-bg-black/60 void-text-white void-px-4 void-py-2 void-rounded-md void-text-sm", children: [ + /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "void-font-medium", children: currentImage.filename }), + /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "void-text-xs void-opacity-75", children: [ + currentImage.width, + " \xD7 ", + currentImage.height, + " \u2022 ", + formatFileSize(currentImage.size), + images.length > 1 && ` \u2022 ${currentIndex + 1} of ${images.length}` + ] }) + ] }), + images.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "void-absolute void-bottom-16 void-left-1/2 -void-translate-x-1/2 void-flex void-gap-2", children: images.map( + (_, index3) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)( + "button", + { + type: "button", + onClick: () => { + setCurrentIndex(index3); + setScale(1); + setPosition({ x: 0, y: 0 }); + }, + className: ` void-w-2 void-h-2 void-rounded-full void-transition-all ${index3 === currentIndex ? "void-bg-white" : "void-bg-white/40"} hover:void-bg-white/60 `, + "aria-label": `Go to image ${index3 + 1}` + }, + index3 + ) + ) }) + ] + } + ); +}; + +// src2/util/ImageMessageRenderer.tsx +var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1); +var ImageMessageRenderer = ({ + images, + caption +}) => { + const [previewUrls, setPreviewUrls] = (0, import_react18.useState)(/* @__PURE__ */ new Map()); + const [lightboxImageIndex, setLightboxImageIndex] = (0, import_react18.useState)(null); + (0, import_react18.useEffect)(() => { + const urls = /* @__PURE__ */ new Map(); + images.forEach((img) => { + const url = createImageDataUrl(img.data, img.mimeType); + urls.set(img.id, url); + }); + setPreviewUrls(urls); + return () => { + urls.forEach((url) => revokeImageDataUrl(url)); + }; + }, [images]); + const handleImageClick = (0, import_react18.useCallback)((index3) => { + setLightboxImageIndex(index3); + }, []); + const handleCloseLightbox = (0, import_react18.useCallback)(() => { + setLightboxImageIndex(null); + }, []); + const handleNavigate = (0, import_react18.useCallback)((direction) => { + if (lightboxImageIndex === null) return; + if (direction === "prev") { + setLightboxImageIndex(Math.max(0, lightboxImageIndex - 1)); + } else { + setLightboxImageIndex(Math.min(images.length - 1, lightboxImageIndex + 1)); + } + }, [lightboxImageIndex, images.length]); + if (images.length === 0) { + return null; + } + const gridCols = images.length === 1 ? 1 : images.length <= 4 ? 2 : 3; + return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "void-flex void-flex-col void-gap-2", children: [ + caption && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "void-text-void-fg-1 void-whitespace-pre-wrap void-break-words", children: caption }), + /* @__PURE__ */ (0, import_jsx_runtime13.jsx)( + "div", + { + className: ` void-grid void-gap-2 ${gridCols === 1 ? "void-grid-cols-1" : ""} ${gridCols === 2 ? "void-grid-cols-2" : ""} ${gridCols === 3 ? "void-grid-cols-3" : ""} `, + role: "group", + "aria-label": `${images.length} image${images.length !== 1 ? "s" : ""}`, + children: images.map((img, index3) => { + const previewUrl = previewUrls.get(img.id); + if (!previewUrl) { + return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)( + "div", + { + className: "void-aspect-square void-bg-void-bg-2-alt void-rounded-md void-flex void-items-center void-justify-center", + children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "void-text-void-fg-3 void-text-sm", children: "Loading..." }) + }, + img.id + ); + } + return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)( + "div", + { + className: "void-relative void-group void-cursor-pointer", + role: "button", + tabIndex: 0, + onClick: () => handleImageClick(index3), + onKeyDown: (e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + handleImageClick(index3); + } + }, + "aria-label": `Image: ${img.filename}. Click to zoom.`, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime13.jsx)( + "img", + { + src: previewUrl, + alt: img.filename ? `${img.filename} (${img.width}\xD7${img.height})` : `Image ${index3 + 1}`, + className: ` void-w-full void-rounded-md void-object-cover void-transition-transform void-duration-200 group-hover:void-scale-[1.02] ${gridCols === 1 ? "void-max-h-[320px] md:void-max-h-[400px]" : "void-aspect-square void-max-h-[240px] md:void-max-h-[300px]"} `, + loading: "lazy" + } + ), + /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "void-absolute void-bottom-0 void-left-0 void-right-0 void-bg-black/60 void-text-white void-text-xs void-px-2 void-py-1 void-rounded-b-md void-opacity-0 group-hover:void-opacity-100 void-transition-opacity", children: [ + /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "void-truncate", children: img.filename }), + /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "void-text-xs void-opacity-75", children: formatFileSize(img.size) }) + ] }) + ] + }, + img.id + ); + }) + } + ) + ] }), + lightboxImageIndex !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)( + ImageLightbox, + { + images, + initialIndex: lightboxImageIndex, + previewUrls, + onClose: handleCloseLightbox, + onNavigate: handleNavigate + } + ) + ] }); +}; + +// src2/util/PDFMessageRenderer.tsx +var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1); +var formatFileSize3 = (bytes) => { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; +}; +var PDFMessageRenderer = ({ + pdfs, + caption +}) => { + if (pdfs.length === 0) { + return null; + } + const gridCols = pdfs.length === 1 ? 1 : pdfs.length <= 4 ? 2 : 3; + return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "void-flex void-flex-col void-gap-2", children: [ + caption && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "void-text-void-fg-1 void-whitespace-pre-wrap void-break-words", children: caption }), + /* @__PURE__ */ (0, import_jsx_runtime14.jsx)( + "div", + { + className: ` void-grid void-gap-2 ${gridCols === 1 ? "void-grid-cols-1" : ""} ${gridCols === 2 ? "void-grid-cols-2" : ""} ${gridCols === 3 ? "void-grid-cols-3" : ""} `, + role: "group", + "aria-label": `${pdfs.length} PDF${pdfs.length !== 1 ? "s" : ""}`, + children: pdfs.map((pdf, index3) => { + const hasPreview = pdf.pagePreviews && pdf.pagePreviews.length > 0; + return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)( + "div", + { + className: "void-relative void-group", + role: "button", + tabIndex: 0, + "aria-label": `PDF: ${pdf.filename}`, + children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)( + "div", + { + className: ` void-relative void-bg-void-bg-2-alt void-border void-border-void-border-3 void-rounded-md void-overflow-hidden void-transition-all void-duration-200 group-hover:void-border-void-border-1 ${gridCols === 1 ? "void-max-h-[320px] md:void-max-h-[400px]" : "void-aspect-[3/4] void-max-h-[240px] md:void-max-h-[300px]"} `, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "void-w-full void-h-full void-flex void-items-center void-justify-center void-bg-void-bg-1", children: hasPreview ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)( + "img", + { + src: pdf.pagePreviews[0], + alt: `Page 1 of ${pdf.filename}`, + className: "void-w-full void-h-full void-object-contain", + loading: "lazy" + } + ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(FileText, { className: "void-w-12 void-h-12 void-text-void-fg-3" }) }), + /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "void-absolute void-bottom-0 void-left-0 void-right-0 void-bg-black/60 void-text-white void-text-xs void-px-2 void-py-1.5 void-rounded-b-md void-opacity-0 group-hover:void-opacity-100 void-transition-opacity", children: [ + /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "void-truncate void-font-medium", children: pdf.filename }), + /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "void-flex void-items-center void-justify-between void-mt-0.5", children: [ + /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "void-text-[10px] void-opacity-75", children: pdf.pageCount ? `${pdf.pageCount} page${pdf.pageCount !== 1 ? "s" : ""}` : formatFileSize3(pdf.size) }), + hasPreview && pdf.pagePreviews.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "void-text-[10px] void-opacity-75", children: [ + "+", + pdf.pagePreviews.length - 1, + " more" + ] }) + ] }) + ] }) + ] + } + ) + }, + pdf.id + ); + }) + } + ) + ] }); +}; + +// src2/sidebar-tsx/SidebarChat.tsx +var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1); +var IconX = ({ size: size3, className = "", ...props }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "svg", + { + xmlns: "http://www.w3.org/2000/svg", + width: size3, + height: size3, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + className, + ...props, + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "path", + { + strokeLinecap: "round", + strokeLinejoin: "round", + d: "M6 18 18 6M6 6l12 12" + } + ) + } + ); +}; +var IconArrowUp = ({ size: size3, className = "" }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "svg", + { + width: size3, + height: size3, + className, + viewBox: "0 0 20 20", + fill: "none", + xmlns: "http://www.w3.org/2000/svg", + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "path", + { + fill: "black", + fillRule: "evenodd", + clipRule: "evenodd", + d: "M5.293 9.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 7.414V15a1 1 0 11-2 0V7.414L6.707 9.707a1 1 0 01-1.414 0z" + } + ) + } + ); +}; +var IconSquare = ({ size: size3, className = "" }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "svg", + { + className, + stroke: "black", + fill: "black", + strokeWidth: "0", + viewBox: "0 0 24 24", + width: size3, + height: size3, + xmlns: "http://www.w3.org/2000/svg", + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("rect", { x: "2", y: "2", width: "20", height: "20", rx: "4", ry: "4" }) + } + ); +}; +var IconWarning = ({ size: size3, className = "" }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "svg", + { + className, + stroke: "currentColor", + fill: "currentColor", + strokeWidth: "0", + viewBox: "0 0 16 16", + width: size3, + height: size3, + xmlns: "http://www.w3.org/2000/svg", + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "path", + { + fillRule: "evenodd", + clipRule: "evenodd", + d: "M7.56 1h.88l6.54 12.26-.44.74H1.44L1 13.26 7.56 1zM8 2.28L2.28 13H13.7L8 2.28zM8.625 12v-1h-1.25v1h1.25zm-1.25-2V6h1.25v4h-1.25z" + } + ) + } + ); +}; +var IconLoading = ({ className = "", showTokenCount }) => { + const [dots, setDots] = (0, import_react19.useState)(1); + (0, import_react19.useEffect)(() => { + let frameId; + let lastUpdate = Date.now(); + const animate = () => { + const now = Date.now(); + if (now - lastUpdate >= 400) { + setDots((prev) => prev >= 3 ? 1 : prev + 1); + lastUpdate = now; + } + frameId = requestAnimationFrame(animate); + }; + frameId = requestAnimationFrame(animate); + return () => cancelAnimationFrame(frameId); + }, []); + const dotsText = ".".repeat(dots); + const tokenText = showTokenCount !== void 0 ? ` (${showTokenCount} tokens)` : ""; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `${className}`, children: [ + dotsText, + tokenText + ] }); +}; +var ReasoningOptionSlider = ({ featureName }) => { + const accessor = useAccessor(); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + const voidSettingsState = useSettingsState(); + const modelSelection = voidSettingsState.modelSelectionOfFeature[featureName]; + const overridesOfModel = voidSettingsState.overridesOfModel; + if (!modelSelection) return null; + if (!isValidProviderModelSelection(modelSelection)) { + return null; + } + const { modelName, providerName } = modelSelection; + const { reasoningCapabilities } = getModelCapabilities(providerName, modelName, overridesOfModel); + const { canTurnOffReasoning, reasoningSlider: reasoningBudgetSlider } = reasoningCapabilities || {}; + const modelSelectionOptions = voidSettingsState.optionsOfModelSelection[featureName][providerName]?.[modelName]; + const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions, overridesOfModel); + if (canTurnOffReasoning && !reasoningBudgetSlider) { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none void-inline-block void-w-10 void-pr-1", children: "Thinking" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + VoidSwitch, + { + size: "xxs", + value: isReasoningEnabled, + onChange: (newVal) => { + const isOff = canTurnOffReasoning && !newVal; + cortexideSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff }); + } + } + ) + ] }); + } + if (reasoningBudgetSlider?.type === "budget_slider") { + const { min: min_, max, default: defaultVal } = reasoningBudgetSlider; + const nSteps = 8; + const stepSize = Math.round((max - min_) / nSteps); + const valueIfOff = min_ - stepSize; + const min = canTurnOffReasoning ? valueIfOff : min_; + const value = isReasoningEnabled ? voidSettingsState.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]?.reasoningBudget ?? defaultVal : valueIfOff; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none void-inline-block void-w-10 void-pr-1", children: "Thinking" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + VoidSlider, + { + width: 50, + size: "xs", + min, + max, + step: stepSize, + value, + onChange: (newVal) => { + if (modelSelection.providerName === "auto" && modelSelection.modelName === "auto") return; + const isOff = canTurnOffReasoning && newVal === valueIfOff; + cortexideSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff, reasoningBudget: newVal }); + } + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: isReasoningEnabled ? `${value} tokens` : "Thinking disabled" }) + ] }); + } + if (reasoningBudgetSlider?.type === "effort_slider") { + const { values, default: defaultVal } = reasoningBudgetSlider; + const min = canTurnOffReasoning ? -1 : 0; + const max = values.length - 1; + const currentEffort = voidSettingsState.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName]?.reasoningEffort ?? defaultVal; + const valueIfOff = -1; + const value = isReasoningEnabled && currentEffort ? values.indexOf(currentEffort) : valueIfOff; + const currentEffortCapitalized = currentEffort.charAt(0).toUpperCase() + currentEffort.slice(1, Infinity); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none void-inline-block void-w-10 void-pr-1", children: "Thinking" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + VoidSlider, + { + width: 30, + size: "xs", + min, + max, + step: 1, + value, + onChange: (newVal) => { + if (modelSelection.providerName === "auto" && modelSelection.modelName === "auto") return; + const isOff = canTurnOffReasoning && newVal === valueIfOff; + cortexideSettingsService.setOptionsOfModelSelection(featureName, modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !isOff, reasoningEffort: values[newVal] ?? void 0 }); + } + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: isReasoningEnabled ? `${currentEffortCapitalized}` : "Thinking disabled" }) + ] }); + } + return null; +}; +var nameOfChatMode = { + "normal": "Chat", + "gather": "Gather", + "agent": "Agent" +}; +var detailOfChatMode = { + "normal": "Normal chat", + "gather": "Reads files, but can't edit", + "agent": "Edits files and uses tools" +}; +var ChatModeDropdown = ({ className }) => { + const accessor = useAccessor(); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + const settingsState = useSettingsState(); + const options2 = (0, import_react19.useMemo)(() => ["normal", "gather", "agent"], []); + const onChangeOption = (0, import_react19.useCallback)((newVal) => { + cortexideSettingsService.setGlobalSetting("chatMode", newVal); + }, [cortexideSettingsService]); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + VoidCustomDropdownBox, + { + className, + options: options2, + selectedOption: settingsState.globalSettings.chatMode, + onChangeOption, + getOptionDisplayName: (val) => nameOfChatMode[val], + getOptionDropdownName: (val) => nameOfChatMode[val], + getOptionDropdownDetail: (val) => detailOfChatMode[val], + getOptionsEqual: (a, b) => a === b + } + ); +}; +var VoidChatArea = ({ + children, + onSubmit, + onAbort, + onClose, + onClickAnywhere, + divRef, + isStreaming = false, + isDisabled = false, + className = "", + showModelDropdown = true, + showSelections = false, + showProspectiveSelections = false, + selections, + setSelections, + imageAttachments, + onImagePaste, + onImageDrop, + onImageUpload, + onPDFDrop, + pdfAttachments, + featureName, + loadingIcon +}) => { + const [isDragOver, setIsDragOver] = import_react19.default.useState(false); + const imageInputRef = import_react19.default.useRef(null); + const pdfInputRef = import_react19.default.useRef(null); + const containerRef = import_react19.default.useRef(null); + import_react19.default.useEffect(() => { + const handlePaste = (e) => { + const items = Array.from(e.clipboardData?.items || []); + const imageFiles = []; + const pdfFiles = []; + for (const item of items) { + if (item.type.startsWith("image/")) { + const file = item.getAsFile(); + if (file) { + imageFiles.push(file); + } + } else if (item.type === "application/pdf") { + const file = item.getAsFile(); + if (file) { + pdfFiles.push(file); + } + } + } + if (imageFiles.length > 0 && onImagePaste) { + e.preventDefault(); + onImagePaste(imageFiles); + } + if (pdfFiles.length > 0 && onPDFDrop) { + e.preventDefault(); + onPDFDrop(pdfFiles); + } + }; + const container = containerRef.current || divRef?.current; + if (container) { + container.addEventListener("paste", handlePaste); + return () => { + container.removeEventListener("paste", handlePaste); + }; + } + }, [divRef, onImagePaste]); + const lastDragOverTimeRef = import_react19.default.useRef(0); + const DRAG_THROTTLE_MS = 50; + const handleDragOver = import_react19.default.useCallback((e) => { + e.preventDefault(); + e.stopPropagation(); + const now = Date.now(); + if (now - lastDragOverTimeRef.current < DRAG_THROTTLE_MS) { + return; + } + lastDragOverTimeRef.current = now; + const hasFiles = Array.from(e.dataTransfer.items).some( + (item) => item.type.startsWith("image/") || item.type === "application/pdf" + ); + if (hasFiles) { + setIsDragOver(true); + } + }, []); + const handleDragLeave = (e) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragOver(false); + }; + const handleDrop = (e) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragOver(false); + const imageFiles = Array.from(e.dataTransfer.files).filter( + (file) => file.type.startsWith("image/") + ); + const pdfFiles = Array.from(e.dataTransfer.files).filter( + (file) => file.type === "application/pdf" + ); + if (imageFiles.length > 0 && onImageDrop) { + onImageDrop(imageFiles); + } + if (pdfFiles.length > 0 && onPDFDrop) { + onPDFDrop(pdfFiles); + } + }; + const handleImageUploadClick = () => { + imageInputRef.current?.click(); + }; + const handlePDFUploadClick = () => { + pdfInputRef.current?.click(); + }; + const handleImageInputChange = (e) => { + const files = Array.from(e.target.files || []).filter( + (file) => file.type.startsWith("image/") + ); + if (files.length > 0 && onImageDrop) { + onImageDrop(files); + } + e.target.value = ""; + }; + const handlePDFInputChange = (e) => { + const files = Array.from(e.target.files || []).filter( + (file) => file.type === "application/pdf" + ); + if (files.length > 0 && onPDFDrop) { + onPDFDrop(files); + } + e.target.value = ""; + }; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + ref: (node) => { + if (divRef) { + if (typeof divRef === "function") { + divRef(node); + } else { + divRef.current = node; + } + } + containerRef.current = node; + }, + className: ` void-gap-x-1 void-flex void-flex-col void-p-2.5 void-relative void-input void-text-left void-shrink-0 void-rounded-2xl void-bg-[#030304] void-transition-all void-duration-200 void-border void-border-[rgba(255,255,255,0.08)] focus-within:void-border-[rgba(255,255,255,0.12)] hover:void-border-[rgba(255,255,255,0.12)] ${isDragOver ? "void-border-blue-500 void-bg-blue-500/10" : ""} void-max-h-[80vh] void-overflow-y-auto ${className} `, + onClick: (e) => { + onClickAnywhere?.(); + }, + onDragOver: handleDragOver, + onDragLeave: handleDragLeave, + onDrop: handleDrop, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "input", + { + ref: imageInputRef, + type: "file", + accept: "image/png,image/jpeg,image/webp,image/gif,image/svg+xml", + multiple: true, + className: "void-hidden", + onChange: handleImageInputChange + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "input", + { + ref: pdfInputRef, + type: "file", + accept: "application/pdf", + multiple: true, + className: "void-hidden", + onChange: handlePDFInputChange + } + ), + imageAttachments, + pdfAttachments, + showSelections && selections && setSelections && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + SelectedFiles, + { + type: "staging", + selections, + setSelections, + showProspectiveSelections + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-relative void-w-full void-flex void-items-end void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex-1 void-min-w-0", children }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-1 void-flex-shrink-0 void-pb-0.5", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + type: "button", + onClick: handleImageUploadClick, + className: "void-flex-shrink-0 void-p-1.5 void-rounded hover:void-bg-void-bg-2-alt void-text-void-fg-4 hover:void-text-void-fg-2 void-transition-colors disabled:void-opacity-40 disabled:void-cursor-not-allowed", + "aria-label": "Upload images", + title: "Upload images (or paste/drag & drop)", + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Image$1, { size: 16 }) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + type: "button", + onClick: handlePDFUploadClick, + className: "void-flex-shrink-0 void-p-1.5 void-rounded hover:void-bg-void-bg-2-alt void-text-void-fg-4 hover:void-text-void-fg-2 void-transition-colors disabled:void-opacity-40 disabled:void-cursor-not-allowed", + "aria-label": "Upload PDFs", + title: "Upload PDFs (or paste/drag & drop)", + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(FileText, { size: 16 }) + } + ), + isStreaming ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ButtonStop, { onClick: onAbort }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ButtonSubmit, + { + onClick: onSubmit, + disabled: isDisabled + } + ) + ] }), + onClose && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-absolute -void-top-1 -void-right-1 void-cursor-pointer void-z-1", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + IconX, + { + size: 12, + className: "void-stroke-[2] void-opacity-80 void-text-void-fg-3 hover:void-brightness-95", + onClick: onClose + } + ) }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-flex-row void-justify-between void-items-center void-gap-2 void-mt-1 void-pt-1 void-border-t void-border-void-border-3/50", children: [ + showModelDropdown && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-flex-wrap void-gap-x-2 void-gap-y-1 void-text-nowrap void-flex-1 void-min-w-0", children: [ + featureName === "Chat" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ChatModeDropdown, { className: "void-text-xs void-text-void-fg-3 void-bg-void-bg-1 void-border void-border-void-border-2 void-rounded void-py-0.5 void-px-1.5" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ModelDropdown, { featureName, className: "void-text-xs void-text-void-fg-3 void-bg-void-bg-1 void-rounded" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ReasoningOptionSlider, { featureName }) + ] }), + isStreaming && loadingIcon && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex void-items-center", children: loadingIcon }) + ] }) + ] + } + ); +}; +var DEFAULT_BUTTON_SIZE = 22; +var ButtonSubmit = ({ className, disabled, ...props }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + type: "button", + className: `void-rounded-full void-flex-shrink-0 void-flex-grow-0 void-flex void-items-center void-justify-center ${disabled ? "void-bg-vscode-disabled-fg void-cursor-default" : "void-bg-white void-cursor-pointer"} ${className} `, + ...props, + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconArrowUp, { size: DEFAULT_BUTTON_SIZE, className: "void-stroke-[2] void-p-[2px]" }) + } + ); +}; +var ButtonStop = ({ className, ...props }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + className: `void-rounded-full void-flex-shrink-0 void-flex-grow-0 void-cursor-pointer void-flex void-items-center void-justify-center void-bg-white ${className} `, + type: "button", + ...props, + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconSquare, { size: DEFAULT_BUTTON_SIZE, className: "void-stroke-[3] void-p-[7px]" }) + } + ); +}; +var scrollToBottom = (divRef) => { + if (divRef.current) { + divRef.current.scrollTop = divRef.current.scrollHeight; + } +}; +var ScrollToBottomContainer = ({ children, className, style, scrollContainerRef }) => { + const [isAtBottom, setIsAtBottom] = (0, import_react19.useState)(true); + const divRef = scrollContainerRef; + const onScroll = () => { + const div = divRef.current; + if (!div) return; + const isBottom = Math.abs( + div.scrollHeight - div.clientHeight - div.scrollTop + ) < 4; + setIsAtBottom(isBottom); + }; + (0, import_react19.useEffect)(() => { + if (isAtBottom) { + scrollToBottom(divRef); + } + }, [children, isAtBottom]); + (0, import_react19.useEffect)(() => { + scrollToBottom(divRef); + }, []); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + ref: divRef, + onScroll, + className, + style, + children + } + ); +}; +var getRelative = (uri, accessor) => { + const workspaceContextService = accessor.get("IWorkspaceContextService"); + let path; + const isInside = workspaceContextService.isInsideWorkspace(uri); + if (isInside) { + const f = workspaceContextService.getWorkspace().folders.find((f2) => uri.fsPath?.startsWith(f2.uri.fsPath)); + if (f) { + path = uri.fsPath.replace(f.uri.fsPath, ""); + } else { + path = uri.fsPath; + } + } else { + path = uri.fsPath; + } + return path || void 0; +}; +var getFolderName = (pathStr) => { + pathStr = pathStr.replace(/[/\\]+/g, "/"); + const parts = pathStr.split("/"); + const nonEmptyParts = parts.filter((part) => part.length > 0); + if (nonEmptyParts.length === 0) return "/"; + if (nonEmptyParts.length === 1) return nonEmptyParts[0] + "/"; + const lastTwo = nonEmptyParts.slice(-2); + return lastTwo.join("/") + "/"; +}; +var getBasename = (pathStr, parts = 1) => { + pathStr = pathStr.replace(/[/\\]+/g, "/"); + const allParts = pathStr.split("/"); + if (allParts.length === 0) return pathStr; + return allParts.slice(-parts).join("/"); +}; +var voidOpenFileFn = (uri, accessor, range) => { + const commandService = accessor.get("ICommandService"); + const editorService = accessor.get("ICodeEditorService"); + let editorSelection = void 0; + if (range) { + editorSelection = { + startLineNumber: range[0], + startColumn: 1, + endLineNumber: range[1], + endColumn: Number.MAX_SAFE_INTEGER + }; + } + commandService.executeCommand("vscode.open", uri).then(() => { + setTimeout(() => { + if (!editorSelection) return; + const editor = editorService.getActiveCodeEditor(); + if (!editor) return; + editor.setSelection(editorSelection); + editor.revealRange(editorSelection, ScrollType.Immediate); + }, 50); + }); +}; +var SelectedFiles = ({ + type, + selections, + setSelections, + showProspectiveSelections, + messageIdx +}) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const modelReferenceService = accessor.get("ICortexideModelService"); + const { uri: currentURI } = useActiveURI(); + const [recentUris, setRecentUris] = (0, import_react19.useState)([]); + const maxRecentUris = 10; + const maxProspectiveFiles = 3; + (0, import_react19.useEffect)(() => { + if (!currentURI) return; + setRecentUris((prev) => { + const withoutCurrent = prev.filter((uri) => uri.fsPath !== currentURI.fsPath); + const withCurrent = [currentURI, ...withoutCurrent]; + return withCurrent.slice(0, maxRecentUris); + }); + }, [currentURI]); + const [prospectiveSelections, setProspectiveSelections] = (0, import_react19.useState)([]); + (0, import_react19.useEffect)(() => { + const computeRecents = async () => { + const prospectiveURIs = recentUris.filter((uri) => !selections.find((s) => s.type === "File" && s.uri.fsPath === uri.fsPath)).slice(0, maxProspectiveFiles); + const answer = []; + for (const uri of prospectiveURIs) { + answer.push({ + type: "File", + uri, + language: (await modelReferenceService.getModelSafe(uri)).model?.getLanguageId() || "plaintext", + state: { wasAddedAsCurrentFile: false } + }); + } + return answer; + }; + if (type === "staging" && showProspectiveSelections) { + computeRecents().then((a) => setProspectiveSelections(a)); + } else { + setProspectiveSelections([]); + } + }, [recentUris, selections, type, showProspectiveSelections]); + const allSelections = [...selections, ...prospectiveSelections]; + if (allSelections.length === 0) { + return null; + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex void-items-center void-flex-wrap void-text-left void-relative void-gap-x-0.5 void-gap-y-1 void-pb-0.5", children: allSelections.map((selection, i) => { + const isThisSelectionProspective = i > selections.length - 1; + const thisKey = selection.type === "CodeSelection" ? selection.type + selection.language + selection.range + selection.state.wasAddedAsCurrentFile + selection.uri.fsPath : selection.type === "File" ? selection.type + selection.language + selection.state.wasAddedAsCurrentFile + selection.uri.fsPath : selection.type === "Folder" ? selection.type + selection.language + selection.state + selection.uri.fsPath : i; + const SelectionIcon = selection.type === "File" ? File : selection.type === "Folder" ? Folder : selection.type === "CodeSelection" ? Text : void 0; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: `void-flex void-flex-col void-space-y-[1px]`, + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "span", + { + className: "void-truncate void-overflow-hidden void-text-ellipsis", + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": getRelative(selection.uri, accessor), + "data-tooltip-place": "top", + "data-tooltip-delay-show": 3e3, + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + className: ` void-flex void-items-center void-gap-1 void-relative void-px-1 void-w-fit void-h-fit void-select-none void-text-xs void-text-nowrap void-border void-rounded-sm ${isThisSelectionProspective ? "void-bg-void-bg-1 void-text-void-fg-3 void-opacity-80" : "void-bg-void-bg-1 hover:void-brightness-95 void-text-void-fg-1"} ${isThisSelectionProspective ? "void-border-void-border-2" : "void-border-void-border-1"} hover:void-border-void-border-1 void-transition-all void-duration-150 `, + onClick: () => { + if (type !== "staging") return; + if (isThisSelectionProspective) { + setSelections([...selections, selection]); + } else if (selection.type === "File") { + voidOpenFileFn(selection.uri, accessor); + const wasAddedAsCurrentFile = selection.state.wasAddedAsCurrentFile; + if (wasAddedAsCurrentFile) { + const newSelection = { ...selection, state: { ...selection.state, wasAddedAsCurrentFile: false } }; + setSelections( + [ + ...selections.slice(0, i), + newSelection, + ...selections.slice(i + 1) + ] + ); + } + } else if (selection.type === "CodeSelection") { + voidOpenFileFn(selection.uri, accessor, selection.range); + } else if (selection.type === "Folder") ; + }, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SelectionIcon, { size: 10 }), + // file name and range + getBasename(selection.uri.fsPath) + (selection.type === "CodeSelection" ? ` (${selection.range[0]}-${selection.range[1]})` : ""), + selection.type === "File" && selection.state.wasAddedAsCurrentFile && messageIdx === void 0 && currentURI?.fsPath === selection.uri.fsPath ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: `void-text-[8px] void-'void-opacity-60 void-text-void-fg-4`, children: `(Current File)` }) : null, + type === "staging" && !isThisSelectionProspective ? ( + // X button + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: "void-cursor-pointer void-z-1 void-self-stretch void-flex void-items-center void-justify-center", + onClick: (e) => { + e.stopPropagation(); + if (type !== "staging") return; + setSelections([...selections.slice(0, i), ...selections.slice(i + 1)]); + }, + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + IconX, + { + className: "void-stroke-[2]", + size: 10 + } + ) + } + ) + ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, {}) + ] + } + ) + } + ) + }, + thisKey + ); + }) }); +}; +var ToolHeaderWrapper = ({ + icon, + title, + desc1, + desc1OnClick, + desc1Info, + desc2, + numResults, + hasNextPage, + children, + info, + bottomChildren, + isError, + onClick, + desc2OnClick, + isOpen, + isRejected, + className + // applies to the main content +}) => { + const [isOpen_, setIsOpen] = (0, import_react19.useState)(false); + const isExpanded = isOpen !== void 0 ? isOpen : isOpen_; + const isDropdown = children !== void 0; + const isClickable = !!(isDropdown || onClick); + const isDesc1Clickable = !!desc1OnClick; + const desc1HTML = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "span", + { + className: `void-text-void-fg-4 void-text-xs void-italic void-truncate void-ml-2 ${isDesc1Clickable ? "void-cursor-pointer hover:void-brightness-125 void-transition-all void-duration-150" : ""} `, + onClick: desc1OnClick, + ...desc1Info ? { + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": desc1Info, + "data-tooltip-place": "top", + "data-tooltip-delay-show": 1e3 + } : {}, + children: desc1 + } + ); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `void-w-full void-border void-border-void-border-3 void-rounded void-px-2 void-py-1 void-bg-void-bg-3 void-overflow-hidden ${className}`, children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `void-select-none void-flex void-items-center void-min-h-[24px]`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `void-flex void-items-center void-w-full void-gap-x-2 void-overflow-hidden void-justify-between ${isRejected ? "void-line-through" : ""}`, children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + className: "void-ml-1 void-flex void-items-center void-overflow-hidden", + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + className: ` void-flex void-items-center void-min-w-0 void-overflow-hidden void-grow ${isClickable ? "void-cursor-pointer hover:void-brightness-125 void-transition-all void-duration-150" : ""} `, + onClick: () => { + if (isDropdown) { + setIsOpen((v) => !v); + } + if (onClick) { + onClick(); + } + }, + children: [ + isDropdown && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ChevronRight, + { + className: ` void-text-void-fg-3 void-mr-0.5 void-h-4 void-w-4 void-flex-shrink-0 void-transition-transform void-duration-100 void-ease-[cubic-bezier(0.4,0,0.2,1)] ${isExpanded ? "void-rotate-90" : ""} ` + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-3 void-flex-shrink-0", children: title }), + !isDesc1Clickable && desc1HTML + ] + } + ), + isDesc1Clickable && desc1HTML + ] + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2 void-flex-shrink-0", children: [ + info && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + CircleEllipsis, + { + className: "void-ml-2 void-text-void-fg-4 void-opacity-60 void-flex-shrink-0", + size: 14, + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": info, + "data-tooltip-place": "top-end" + } + ), + isError && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + TriangleAlert, + { + className: "void-text-void-warning void-opacity-90 void-flex-shrink-0", + size: 14, + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": "Error running tool", + "data-tooltip-place": "top" + } + ), + isRejected && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + Ban, + { + className: "void-text-void-fg-4 void-opacity-90 void-flex-shrink-0", + size: 14, + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": "Canceled", + "data-tooltip-place": "top" + } + ), + desc2 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-4 void-text-xs", onClick: desc2OnClick, children: desc2 }), + numResults !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-4 void-text-xs void-ml-auto void-mr-1", children: `${numResults}${hasNextPage ? "+" : ""} result${numResults !== 1 ? "s" : ""}` }) + ] }) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: `void-overflow-hidden void-transition-all void-duration-200 void-ease-in-out ${isExpanded ? "void-opacity-100 void-py-1" : "void-max-h-0 void-opacity-0"} void-text-void-fg-4 void-rounded-sm void-overflow-x-auto `, + children + } + ) + ] }), + bottomChildren + ] }); +}; +var EditTool = ({ toolMessage, threadId, messageIdx, content }) => { + const accessor = useAccessor(); + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + const { rawParams, params, name } = toolMessage; + const desc1OnClick = () => voidOpenFileFn(params.uri, accessor); + const componentParams = { title, desc1, desc1OnClick, desc1Info, isError, icon, isRejected }; + const editToolType = toolMessage.name === "edit_file" ? "diff" : "rewrite"; + if (toolMessage.type === "running_now" || toolMessage.type === "tool_request") { + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { className: "void-bg-void-bg-3", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + EditToolChildren, + { + uri: params.uri, + code: content, + type: editToolType + } + ) }); + } else if (toolMessage.type === "success" || toolMessage.type === "rejected" || toolMessage.type === "tool_error") { + const applyBoxId = getApplyBoxId({ + threadId, + messageIdx, + tokenIdx: "N/A" + }); + componentParams.desc2 = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + EditToolHeaderButtons, + { + applyBoxId, + uri: params.uri, + codeStr: content, + toolName: name, + threadId + } + ); + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { className: "void-bg-void-bg-3", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + EditToolChildren, + { + uri: params.uri, + code: content, + type: editToolType + } + ) }); + if (toolMessage.type === "success" || toolMessage.type === "rejected") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Lint errors", children: result?.lintErrors?.map( + (error2, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-whitespace-nowrap", children: [ + "Lines ", + error2.startLineNumber, + "-", + error2.endLineNumber, + ": ", + error2.message + ] }, i) + ) }); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); +}; +var UserMessageComponent = ({ chatMessage, messageIdx, isCheckpointGhost, currCheckpointIdx, _scrollToBottom }) => { + const accessor = useAccessor(); + const chatThreadsService = accessor.get("IChatThreadService"); + const chatThreadsState = useChatThreadsState(); + const currentThreadId = chatThreadsState.currentThreadId; + let isBeingEdited = false; + let stagingSelections = []; + let setIsBeingEdited = (_) => { + }; + let setStagingSelections = (_) => { + }; + if (messageIdx !== void 0) { + const _state = chatThreadsService.getCurrentMessageState(messageIdx); + isBeingEdited = _state.isBeingEdited; + stagingSelections = _state.stagingSelections; + setIsBeingEdited = (v) => chatThreadsService.setCurrentMessageState(messageIdx, { isBeingEdited: v }); + setStagingSelections = (s) => chatThreadsService.setCurrentMessageState(messageIdx, { stagingSelections: s }); + } + const mode = isBeingEdited ? "edit" : "display"; + const [isFocused, setIsFocused] = (0, import_react19.useState)(false); + const [isHovered, setIsHovered] = (0, import_react19.useState)(false); + const [isDisabled, setIsDisabled] = (0, import_react19.useState)(false); + const [textAreaRefState, setTextAreaRef] = (0, import_react19.useState)(null); + const textAreaFnsRef = (0, import_react19.useRef)(null); + const _mustInitialize = (0, import_react19.useRef)(true); + const _justEnabledEdit = (0, import_react19.useRef)(false); + (0, import_react19.useEffect)(() => { + const canInitialize = mode === "edit" && textAreaRefState; + const shouldInitialize = _justEnabledEdit.current || _mustInitialize.current; + if (canInitialize && shouldInitialize) { + setStagingSelections( + (chatMessage.selections || []).map((s) => { + if (s.type === "File") return { ...s, state: { ...s.state, wasAddedAsCurrentFile: false } }; + else + return s; + }) + ); + if (textAreaFnsRef.current) + textAreaFnsRef.current.setValue(chatMessage.displayContent || ""); + textAreaRefState.focus(); + _justEnabledEdit.current = false; + _mustInitialize.current = false; + } + }, [chatMessage, mode, textAreaRefState, setStagingSelections]); + const onOpenEdit = () => { + setIsBeingEdited(true); + chatThreadsService.setCurrentlyFocusedMessageIdx(messageIdx); + _justEnabledEdit.current = true; + }; + const onCloseEdit = () => { + setIsFocused(false); + setIsHovered(false); + setIsBeingEdited(false); + chatThreadsService.setCurrentlyFocusedMessageIdx(void 0); + }; + const EditSymbol = mode === "display" ? Pencil : X; + let chatbubbleContents; + if (mode === "display") { + const hasImages = chatMessage.images && chatMessage.images.length > 0; + const hasPDFs = chatMessage.pdfs && chatMessage.pdfs.length > 0; + chatbubbleContents = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SelectedFiles, { type: "past", messageIdx, selections: chatMessage.selections || [] }), + hasImages && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-px-0.5 void-py-2", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ImageMessageRenderer, + { + images: chatMessage.images + } + ) }), + hasPDFs && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-px-0.5 void-py-2", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + PDFMessageRenderer, + { + pdfs: chatMessage.pdfs + } + ) }), + chatMessage.displayContent && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-px-0.5", children: chatMessage.displayContent }) + ] }); + } else if (mode === "edit") { + const onSubmit = async () => { + if (isDisabled) return; + if (!textAreaRefState) return; + if (messageIdx === void 0) return; + const threadId = currentThreadId; + const thread = chatThreadsState.allThreads[threadId]; + if (!thread || !thread.messages || thread.messages[messageIdx]?.role !== "user") { + console.error("Error while editing message: Message is not a user message or no longer exists"); + setIsBeingEdited(false); + chatThreadsService.setCurrentlyFocusedMessageIdx(void 0); + return; + } + await chatThreadsService.abortRunning(threadId); + setIsBeingEdited(false); + chatThreadsService.setCurrentlyFocusedMessageIdx(void 0); + const userMessage = textAreaRefState.value; + try { + await chatThreadsService.editUserMessageAndStreamResponse({ userMessage, messageIdx, threadId }); + } catch (e) { + console.error("Error while editing message:", e); + } + await chatThreadsService.focusCurrentChat(); + requestAnimationFrame(() => _scrollToBottom?.()); + }; + const onAbort = async () => { + const threadId = currentThreadId; + await chatThreadsService.abortRunning(threadId); + }; + const onKeyDown = (e) => { + if (e.key === "Escape") { + onCloseEdit(); + } + if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) { + onSubmit(); + } + }; + if (!chatMessage.content) { + return null; + } + chatbubbleContents = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + VoidChatArea, + { + featureName: "Chat", + onSubmit, + onAbort, + isStreaming: false, + isDisabled, + showSelections: true, + showProspectiveSelections: false, + selections: stagingSelections, + setSelections: setStagingSelections, + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + VoidInputBox2, + { + enableAtToMention: true, + appearance: "chatDark", + ref: setTextAreaRef, + className: "void-min-h-[60px] void-px-3 void-py-3 void-rounded-2xl", + placeholder: "Plan, @ for context, / for commands", + onChangeText: (text) => setIsDisabled(!text), + onFocus: () => { + setIsFocused(true); + chatThreadsService.setCurrentlyFocusedMessageIdx(messageIdx); + }, + onBlur: () => { + setIsFocused(false); + }, + onKeyDown, + fnsRef: textAreaFnsRef, + multiline: true + } + ) + } + ); + } + const isMsgAfterCheckpoint = currCheckpointIdx !== void 0 && currCheckpointIdx === messageIdx - 1; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + className: ` void-relative void-ml-auto ${mode === "edit" ? "void-w-full void-max-w-full" : mode === "display" ? `void-self-end void-w-fit void-max-w-full void-whitespace-pre-wrap` : ""} ${isCheckpointGhost && !isMsgAfterCheckpoint ? "void-opacity-50 void-pointer-events-none" : ""} `, + onMouseEnter: () => setIsHovered(true), + onMouseLeave: () => setIsHovered(false), + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: ` void-text-left void-rounded-lg void-max-w-full ${mode === "edit" ? "" : mode === "display" ? "void-p-2 void-flex void-flex-col void-bg-void-bg-1 void-text-void-fg-1 void-overflow-x-auto void-cursor-pointer" : ""} `, + onClick: () => { + if (mode === "display") { + onOpenEdit(); + } + }, + children: chatbubbleContents + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: "void-absolute -void-top-1 -void-right-1 void-translate-x-0 -void-translate-y-0 void-z-1", + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + EditSymbol, + { + size: 18, + className: ` void-cursor-pointer void-p-[2px] void-bg-void-bg-1 void-border void-border-void-border-1 void-rounded-md void-transition-opacity void-duration-200 void-ease-in-out ${isHovered || isFocused && mode === "edit" ? "void-opacity-100" : "void-opacity-0"} `, + onClick: () => { + if (mode === "display") { + onOpenEdit(); + } else if (mode === "edit") { + onCloseEdit(); + } + } + } + ) + } + ) + ] + } + ); +}; +var SmallProseWrapper = ({ children }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: " void-text-void-fg-4 void-prose void-prose-sm void-break-words void-max-w-none void-leading-snug void-text-[13px] [&>:first-child]:!void-mt-0 [&>:last-child]:!void-mb-0 prose-h1:void-text-[14px] prose-h1:void-my-4 prose-h2:void-text-[13px] prose-h2:void-my-4 prose-h3:void-text-[13px] prose-h3:void-my-3 prose-h4:void-text-[13px] prose-h4:void-my-2 prose-p:void-my-2 prose-p:void-leading-snug prose-hr:void-my-2 prose-ul:void-my-2 prose-ul:void-pl-4 prose-ul:void-list-outside prose-ul:void-list-disc prose-ul:void-leading-snug prose-ol:void-my-2 prose-ol:void-pl-4 prose-ol:void-list-outside prose-ol:void-list-decimal prose-ol:void-leading-snug marker:void-text-inherit prose-blockquote:void-pl-2 prose-blockquote:void-my-2 prose-code:void-text-void-fg-3 prose-code:void-text-[12px] prose-code:before:void-content-none prose-code:after:void-content-none prose-pre:void-text-[12px] prose-pre:void-p-2 prose-pre:void-my-2 prose-table:void-text-[13px] ", children }); +}; +var ProseWrapper = ({ children }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: " void-text-void-fg-2 void-prose void-prose-sm void-break-words prose-p:void-block prose-hr:void-my-4 prose-pre:void-my-2 marker:void-text-inherit prose-ol:void-list-outside prose-ol:void-list-decimal prose-ul:void-list-outside prose-ul:void-list-disc prose-li:void-my-0 prose-code:before:void-content-none prose-code:after:void-content-none prose-headings:void-prose-sm prose-headings:void-font-bold prose-p:void-leading-normal prose-ol:void-leading-normal prose-ul:void-leading-normal void-max-w-none ", children }); +}; +var AssistantMessageComponent = import_react19.default.memo(({ chatMessage, isCheckpointGhost, isCommitted, messageIdx }) => { + const accessor = useAccessor(); + const chatThreadsService = accessor.get("IChatThreadService"); + const reasoningStr = chatMessage.reasoning?.trim() || null; + const hasReasoning = !!reasoningStr; + const isDoneReasoning = !!chatMessage.displayContent; + const thread = chatThreadsService.getCurrentThread(); + const chatMessageLocation = (0, import_react19.useMemo)(() => ({ + threadId: thread.id, + messageIdx + }), [thread.id, messageIdx]); + const isEmpty = !chatMessage.displayContent && !chatMessage.reasoning; + if (isEmpty) return null; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [ + hasReasoning && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${isCheckpointGhost ? "void-opacity-50" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ReasoningWrapper, { isDoneReasoning, isStreaming: !isCommitted, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SmallProseWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ChatMarkdownRender, + { + string: reasoningStr, + chatMessageLocation, + isApplyEnabled: false, + isLinkDetectionEnabled: true + } + ) }) }) }), + chatMessage.displayContent && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${isCheckpointGhost ? "void-opacity-50" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ProseWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ChatMarkdownRender, + { + string: chatMessage.displayContent || "", + chatMessageLocation, + isApplyEnabled: true, + isLinkDetectionEnabled: true + } + ) }) }) + ] }); +}, (prev, next) => { + return prev.chatMessage.displayContent === next.chatMessage.displayContent && prev.chatMessage.reasoning === next.chatMessage.reasoning && prev.isCheckpointGhost === next.isCheckpointGhost && prev.isCommitted === next.isCommitted && prev.messageIdx === next.messageIdx; +}); +var ReasoningWrapper = ({ isDoneReasoning, isStreaming, children }) => { + const isDone = isDoneReasoning || !isStreaming; + const isWriting = !isDone; + const [isOpen, setIsOpen] = (0, import_react19.useState)(isWriting); + (0, import_react19.useEffect)(() => { + if (!isWriting) setIsOpen(false); + }, [isWriting]); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { title: "Reasoning", desc1: isWriting ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconLoading, {}) : "", isOpen, onClick: () => setIsOpen((v) => !v), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "!void-select-text void-cursor-auto", children }) }) }); +}; +var loadingTitleWrapper = (item) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "void-flex void-items-center void-flex-nowrap", children: [ + item, + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconLoading, { className: "void-w-3 void-text-sm" }) + ] }); +}; +var titleOfBuiltinToolName = { + "read_file": { done: "Read file", proposed: "Read file", running: loadingTitleWrapper("Reading file") }, + "ls_dir": { done: "Inspected folder", proposed: "Inspect folder", running: loadingTitleWrapper("Inspecting folder") }, + "get_dir_tree": { done: "Inspected folder tree", proposed: "Inspect folder tree", running: loadingTitleWrapper("Inspecting folder tree") }, + "search_pathnames_only": { done: "Searched by file name", proposed: "Search by file name", running: loadingTitleWrapper("Searching by file name") }, + "search_for_files": { done: "Searched", proposed: "Search", running: loadingTitleWrapper("Searching") }, + "create_file_or_folder": { done: `Created`, proposed: `Create`, running: loadingTitleWrapper(`Creating`) }, + "delete_file_or_folder": { done: `Deleted`, proposed: `Delete`, running: loadingTitleWrapper(`Deleting`) }, + "edit_file": { done: `Edited file`, proposed: "Edit file", running: loadingTitleWrapper("Editing file") }, + "rewrite_file": { done: `Wrote file`, proposed: "Write file", running: loadingTitleWrapper("Writing file") }, + "run_command": { done: `Ran terminal`, proposed: "Run terminal", running: loadingTitleWrapper("Running terminal") }, + "run_persistent_command": { done: `Ran terminal`, proposed: "Run terminal", running: loadingTitleWrapper("Running terminal") }, + "open_persistent_terminal": { done: `Opened terminal`, proposed: "Open terminal", running: loadingTitleWrapper("Opening terminal") }, + "kill_persistent_terminal": { done: `Killed terminal`, proposed: "Kill terminal", running: loadingTitleWrapper("Killing terminal") }, + "read_lint_errors": { done: `Read lint errors`, proposed: "Read lint errors", running: loadingTitleWrapper("Reading lint errors") }, + "search_in_file": { done: "Searched in file", proposed: "Search in file", running: loadingTitleWrapper("Searching in file") }, + "web_search": { done: "Searched the web", proposed: "Search the web", running: loadingTitleWrapper("Searching the web") }, + "browse_url": { done: "Fetched web page", proposed: "Fetch web page", running: loadingTitleWrapper("Fetching web page") } +}; +var getTitle = (toolMessage) => { + const t = toolMessage; + if (!builtinToolNames.includes(t.name)) { + const descriptor = t.type === "success" ? "Called" : t.type === "running_now" ? "Calling" : t.type === "tool_request" ? "Call" : t.type === "rejected" ? "Call" : t.type === "invalid_params" ? "Call" : t.type === "tool_error" ? "Call" : "Call"; + const title = `${descriptor} ${toolMessage.mcpServerName || "MCP"}`; + if (t.type === "running_now" || t.type === "tool_request") + return loadingTitleWrapper(title); + return title; + } else { + const toolName = t.name; + if (t.type === "success") return titleOfBuiltinToolName[toolName].done; + if (t.type === "running_now") return titleOfBuiltinToolName[toolName].running; + return titleOfBuiltinToolName[toolName].proposed; + } +}; +var toolNameToDesc = (toolName, _toolParams, accessor) => { + if (!_toolParams) { + return { desc1: "" }; + } + const x = { + "read_file": () => { + const toolParams = _toolParams; + return { + desc1: getBasename(toolParams.uri.fsPath), + desc1Info: getRelative(toolParams.uri, accessor) + }; + }, + "ls_dir": () => { + const toolParams = _toolParams; + return { + desc1: getFolderName(toolParams.uri.fsPath), + desc1Info: getRelative(toolParams.uri, accessor) + }; + }, + "search_pathnames_only": () => { + const toolParams = _toolParams; + return { + desc1: `"${toolParams.query}"` + }; + }, + "search_for_files": () => { + const toolParams = _toolParams; + return { + desc1: `"${toolParams.query}"` + }; + }, + "search_in_file": () => { + const toolParams = _toolParams; + return { + desc1: `"${toolParams.query}"`, + desc1Info: getRelative(toolParams.uri, accessor) + }; + }, + "create_file_or_folder": () => { + const toolParams = _toolParams; + return { + desc1: toolParams.isFolder ? getFolderName(toolParams.uri.fsPath) ?? "/" : getBasename(toolParams.uri.fsPath), + desc1Info: getRelative(toolParams.uri, accessor) + }; + }, + "delete_file_or_folder": () => { + const toolParams = _toolParams; + return { + desc1: toolParams.isFolder ? getFolderName(toolParams.uri.fsPath) ?? "/" : getBasename(toolParams.uri.fsPath), + desc1Info: getRelative(toolParams.uri, accessor) + }; + }, + "rewrite_file": () => { + const toolParams = _toolParams; + return { + desc1: getBasename(toolParams.uri.fsPath), + desc1Info: getRelative(toolParams.uri, accessor) + }; + }, + "edit_file": () => { + const toolParams = _toolParams; + return { + desc1: getBasename(toolParams.uri.fsPath), + desc1Info: getRelative(toolParams.uri, accessor) + }; + }, + "run_command": () => { + const toolParams = _toolParams; + return { + desc1: `"${toolParams.command}"` + }; + }, + "run_persistent_command": () => { + const toolParams = _toolParams; + return { + desc1: `"${toolParams.command}"` + }; + }, + "open_persistent_terminal": () => { + return { desc1: "" }; + }, + "kill_persistent_terminal": () => { + const toolParams = _toolParams; + return { desc1: toolParams.persistentTerminalId }; + }, + "get_dir_tree": () => { + const toolParams = _toolParams; + return { + desc1: getFolderName(toolParams.uri.fsPath) ?? "/", + desc1Info: getRelative(toolParams.uri, accessor) + }; + }, + "read_lint_errors": () => { + const toolParams = _toolParams; + return { + desc1: getBasename(toolParams.uri.fsPath), + desc1Info: getRelative(toolParams.uri, accessor) + }; + }, + "web_search": () => { + const toolParams = _toolParams; + return { + desc1: `"${toolParams.query}"` + }; + }, + "browse_url": () => { + const toolParams = _toolParams; + return { + desc1: toolParams.url, + desc1Info: new URL(toolParams.url).hostname + }; + } + }; + try { + return x[toolName]?.() || { desc1: "" }; + } catch { + return { desc1: "" }; + } +}; +var ToolRequestAcceptRejectButtons = ({ toolName }) => { + const accessor = useAccessor(); + const chatThreadsService = accessor.get("IChatThreadService"); + const metricsService = accessor.get("IMetricsService"); + accessor.get("ICortexideSettingsService"); + useSettingsState(); + const chatThreadsState = useChatThreadsState(); + const currentThreadId = chatThreadsState.currentThreadId; + const onAccept = (0, import_react19.useCallback)(() => { + try { + chatThreadsService.approveLatestToolRequest(currentThreadId); + metricsService.capture("Tool Request Accepted", {}); + } catch (e) { + console.error("Error while approving message in chat:", e); + } + }, [chatThreadsService, metricsService, currentThreadId]); + const onReject = (0, import_react19.useCallback)(() => { + try { + chatThreadsService.rejectLatestToolRequest(currentThreadId); + } catch (e) { + console.error("Error while approving message in chat:", e); + } + metricsService.capture("Tool Request Rejected", {}); + }, [chatThreadsService, metricsService, currentThreadId]); + const approveButton = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + onClick: onAccept, + className: ` void-px-2 void-py-1 void-bg-[var(--vscode-button-background)] void-text-[var(--vscode-button-foreground)] hover:void-bg-[var(--vscode-button-hoverBackground)] void-rounded void-text-sm void-font-medium `, + children: "Approve" + } + ); + const cancelButton = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + onClick: onReject, + className: ` void-px-2 void-py-1 void-bg-[var(--vscode-button-secondaryBackground)] void-text-[var(--vscode-button-secondaryForeground)] hover:void-bg-[var(--vscode-button-secondaryHoverBackground)] void-rounded void-text-sm void-font-medium `, + children: "Cancel" + } + ); + const approvalType = isABuiltinToolName(toolName) ? approvalTypeOfBuiltinToolName[toolName] : "MCP tools"; + const approvalToggle = approvalType ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex void-items-center void-ml-2 void-gap-x-1", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolApprovalTypeSwitch, { size: "xs", approvalType, desc: `Auto-approve ${approvalType}` }) }, approvalType) : null; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-gap-2 void-mx-0.5 void-items-center", children: [ + approveButton, + cancelButton, + approvalToggle + ] }); +}; +var ToolChildrenWrapper = ({ children, className }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${className ? className : ""} void-cursor-default void-select-none`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-px-2 void-min-w-full void-overflow-hidden", children }) }); +}; +var CodeChildren = ({ children, className }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${className ?? ""} void-p-1 void-rounded-sm void-overflow-auto void-text-sm`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "!void-select-text void-cursor-auto", children }) }); +}; +var ListableToolItem = ({ name, onClick, isSmall, className, showDot }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + className: ` ${onClick ? "hover:void-brightness-125 hover:void-cursor-pointer void-transition-all void-duration-200 " : ""} void-flex void-items-center void-flex-nowrap void-whitespace-nowrap ${className ? className : ""} `, + onClick, + children: [ + showDot === false ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("svg", { className: "void-w-1 void-h-1 void-opacity-60 void-mr-1.5 void-fill-current", viewBox: "0 0 100 40", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("rect", { x: "0", y: "15", width: "100", height: "10" }) }) }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${isSmall ? "void-italic void-text-void-fg-4 void-flex void-items-center" : ""}`, children: name }) + ] + } + ); +}; +var EditToolChildren = ({ uri, code, type }) => { + const content = type === "diff" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VoidDiffEditor, { uri, searchReplaceBlocks: code }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ChatMarkdownRender, { string: `\`\`\` +${code} +\`\`\``, codeURI: uri, chatMessageLocation: void 0 }); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "!void-select-text void-cursor-auto", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SmallProseWrapper, { children: content }) }); +}; +var LintErrorChildren = ({ lintErrors }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-text-xs void-text-void-fg-4 void-opacity-80 void-border-l-2 void-border-void-warning void-px-2 void-py-0.5 void-flex void-flex-col void-gap-0.5 void-overflow-x-auto void-whitespace-nowrap", children: lintErrors.map( + (error2, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [ + "Lines ", + error2.startLineNumber, + "-", + error2.endLineNumber, + ": ", + error2.message + ] }, i) + ) }); +}; +var BottomChildren = ({ children, title }) => { + const [isOpen, setIsOpen] = (0, import_react19.useState)(false); + if (!children) return null; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-w-full void-px-2 void-mt-0.5", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + className: `void-flex void-items-center void-cursor-pointer void-select-none void-transition-colors void-duration-150 void-pl-0 void-py-0.5 void-rounded void-group`, + onClick: () => setIsOpen((o) => !o), + style: { background: "none" }, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ChevronRight, + { + className: `void-mr-1 void-h-3 void-w-3 void-flex-shrink-0 void-transition-transform void-duration-100 void-text-void-fg-4 group-hover:void-text-void-fg-3 ${isOpen ? "void-rotate-90" : ""}` + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-font-medium void-text-void-fg-4 group-hover:void-text-void-fg-3 void-text-xs", children: title }) + ] + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: `void-overflow-hidden void-transition-all void-duration-200 void-ease-in-out ${isOpen ? "void-opacity-100" : "void-max-h-0 void-opacity-0"} void-text-xs void-pl-4`, + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-overflow-x-auto void-text-void-fg-4 void-opacity-90 void-border-l-2 void-border-void-warning void-px-2 void-py-0.5", children }) + } + ) + ] }); +}; +var EditToolHeaderButtons = ({ applyBoxId, uri, codeStr, toolName, threadId }) => { + const { streamState } = useEditToolStreamState({ applyBoxId, uri }); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-1", children: [ + streamState === "idle-no-changes" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CopyButton, { codeStr, toolTipName: "Copy" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(EditToolAcceptRejectButtonsHTML, { type: toolName, codeStr, applyBoxId, uri, threadId }) + ] }); +}; +var InvalidTool = ({ toolName, message, mcpServerName }) => { + useAccessor(); + const title = getTitle({ name: toolName, type: "invalid_params", mcpServerName }); + const desc1 = "Invalid parameters"; + const icon = null; + const isError = true; + const componentParams = { title, desc1, isError, icon }; + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { className: "void-bg-void-bg-3", children: message }) }); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); +}; +var CanceledTool = ({ toolName, mcpServerName }) => { + useAccessor(); + const title = getTitle({ name: toolName, type: "rejected", mcpServerName }); + const desc1 = ""; + const icon = null; + const isRejected = true; + const componentParams = { title, desc1, icon, isRejected }; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); +}; +var CommandTool = ({ + toolMessage, + type, + threadId +}) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const terminalToolsService = accessor.get("ITerminalToolService"); + const toolsService = accessor.get("IToolsService"); + const isError = false; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + const streamState = useChatThreadsStreamState(threadId); + const divRef = (0, import_react19.useRef)(null); + const isRejected = toolMessage.type === "rejected"; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + const effect = async () => { + if (streamState?.isRunning !== "tool") return; + if (type !== "run_command" || toolMessage.type !== "running_now") return; + await streamState?.interrupt; + const container = divRef.current; + if (!container) return; + const terminal = terminalToolsService.getTemporaryTerminal(toolMessage.params.terminalId); + if (!terminal) return; + try { + terminal.attachToElement(container); + terminal.setVisible(true); + } catch { + } + const resizeObserver = new ResizeObserver((entries) => { + if (!entries[0]) return; + let width; + let height; + if (entries[0].borderBoxSize && entries[0].borderBoxSize.length > 0) { + width = entries[0].borderBoxSize[0].inlineSize; + height = entries[0].borderBoxSize[0].blockSize; + } else if (entries[0].contentRect) { + width = entries[0].contentRect.width; + height = entries[0].contentRect.height; + } else { + const target = entries[0].target; + width = target.clientWidth; + height = target.clientHeight; + } + if (width > 0 && height > 0 && typeof terminal.layout === "function") { + terminal.layout({ width, height }); + } + }); + resizeObserver.observe(container); + return () => { + terminal.detachFromElement(); + resizeObserver?.disconnect(); + }; + }; + (0, import_react19.useEffect)(() => { + effect(); + }, [terminalToolsService, toolMessage, toolMessage.type, type]); + if (toolMessage.type === "success") { + const { result } = toolMessage; + let msg; + if (type === "run_command") msg = toolsService.stringOfResult["run_command"](toolMessage.params, result); + else + msg = toolsService.stringOfResult["run_persistent_command"](toolMessage.params, result); + if (type === "run_persistent_command") { + componentParams.info = persistentTerminalNameOfId(toolMessage.params.persistentTerminalId); + } + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { className: "void-whitespace-pre void-text-nowrap void-overflow-auto void-text-sm", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "!void-select-text void-cursor-auto", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BlockCode, { initValue: `${msg.trim()}`, language: "shellscript" }) }) }); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } else if (toolMessage.type === "running_now") { + if (type === "run_command") + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { ref: divRef, className: "void-relative void-h-[300px] void-text-sm" }); + } else if (toolMessage.type === "rejected" || toolMessage.type === "tool_request") ; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams, isOpen: type === "run_command" && toolMessage.type === "running_now" ? true : void 0 }) }); +}; +var MCPToolWrapper = ({ toolMessage }) => { + const accessor = useAccessor(); + const mcpService = accessor.get("IMCPService"); + const title = getTitle(toolMessage); + const desc1 = removeMCPToolNamePrefix(toolMessage.name); + const icon = null; + if (toolMessage.type === "running_now") return null; + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { rawParams, params } = toolMessage; + const redactParams = (value) => { + const SENSITIVE_KEYS = /* @__PURE__ */ new Set(["token", "apiKey", "apikey", "password", "authorization", "auth", "secret", "clientSecret", "accessToken", "bearer"]); + const redactValue = (v) => typeof v === "string" ? v.length > 6 ? v.slice(0, 3) + "***" + v.slice(-2) : "***" : v; + if (Array.isArray(value)) return value.map(redactParams); + if (value && typeof value === "object") { + const out = Array.isArray(value) ? [] : {}; + for (const k of Object.keys(value)) { + if (SENSITIVE_KEYS.has(k.toLowerCase())) out[k] = redactValue(value[k]); + else + out[k] = redactParams(value[k]); + } + return out; + } + return value; + }; + const componentParams = { title, desc1, isError, icon, isRejected }; + const redactedParams = redactParams(params); + const paramsStr = JSON.stringify(redactedParams, null, 2); + componentParams.desc2 = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CopyButton, { codeStr: paramsStr, toolTipName: `Copy inputs (redacted): ${paramsStr}` }); + componentParams.info = !toolMessage.mcpServerName ? "MCP tool not found" : void 0; + if (toolMessage.type === "success" || toolMessage.type === "tool_request") { + const { result } = toolMessage; + const resultStr = result ? mcpService.stringifyResult(result) : "null"; + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SmallProseWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ChatMarkdownRender, + { + string: `\`\`\`json +${resultStr} +\`\`\``, + chatMessageLocation: void 0, + isApplyEnabled: false, + isLinkDetectionEnabled: true + } + ) }) }); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); +}; +var builtinToolNameToComponent = { + "read_file": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") return null; + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + let range = void 0; + if (toolMessage.params.startLine !== null || toolMessage.params.endLine !== null) { + const start = toolMessage.params.startLine === null ? `1` : `${toolMessage.params.startLine}`; + const end = toolMessage.params.endLine === null ? `` : `${toolMessage.params.endLine}`; + const addStr = `(${start}-${end})`; + componentParams.desc1 += ` ${addStr}`; + range = [params.startLine || 1, params.endLine || 1]; + } + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor, range); + }; + if (result.hasNextPage && params.pageNumber === 1) + componentParams.desc2 = `(truncated after ${Math.round(MAX_FILE_CHARS_PAGE) / 1e3}k)`; + else if (params.pageNumber > 1) + componentParams.desc2 = `(part ${params.pageNumber})`; + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "get_dir_tree": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") return null; + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + if (params.uri) { + const rel = getRelative(params.uri, accessor); + if (rel) componentParams.info = `Only search in ${rel}`; + } + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SmallProseWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ChatMarkdownRender, + { + string: `\`\`\` +${result.str} +\`\`\``, + chatMessageLocation: void 0, + isApplyEnabled: false, + isLinkDetectionEnabled: true + } + ) }) }); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "ls_dir": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + accessor.get("IExplorerService"); + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") return null; + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + if (params.uri) { + const rel = getRelative(params.uri, accessor); + if (rel) componentParams.info = `Only search in ${rel}`; + } + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.numResults = result.children?.length; + componentParams.hasNextPage = result.hasNextPage; + componentParams.children = !result.children || (result.children.length ?? 0) === 0 ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(ToolChildrenWrapper, { children: [ + result.children.map( + (child, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ListableToolItem, + { + name: `${child.name}${child.isDirectory ? "/" : ""}`, + className: "void-w-full void-overflow-auto", + onClick: () => { + voidOpenFileFn(child.uri, accessor); + } + }, + i + ) + ), + result.hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ListableToolItem, { name: `Results truncated (${result.itemsRemaining} remaining).`, isSmall: true, className: "void-w-full void-overflow-auto" }) + ] }); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "search_pathnames_only": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") return null; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + if (params.includePattern) { + componentParams.info = `Only search in ${params.includePattern}`; + } + if (toolMessage.type === "success") { + const { result, rawParams: rawParams2 } = toolMessage; + componentParams.numResults = result.uris.length; + componentParams.hasNextPage = result.hasNextPage; + componentParams.children = result.uris.length === 0 ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(ToolChildrenWrapper, { children: [ + result.uris.map( + (uri, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ListableToolItem, + { + name: getBasename(uri.fsPath), + className: "void-w-full void-overflow-auto", + onClick: () => { + voidOpenFileFn(uri, accessor); + } + }, + i + ) + ), + result.hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ListableToolItem, { name: "Results truncated.", isSmall: true, className: "void-w-full void-overflow-auto" }) + ] }); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "search_for_files": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") return null; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + if (params.searchInFolder || params.isRegex) { + let info = []; + if (params.searchInFolder) { + const rel = getRelative(params.searchInFolder, accessor); + if (rel) info.push(`Only search in ${rel}`); + } + if (params.isRegex) { + info.push(`Uses regex search`); + } + componentParams.info = info.join("; "); + } + if (toolMessage.type === "success") { + const { result, rawParams: rawParams2 } = toolMessage; + componentParams.numResults = result.uris.length; + componentParams.hasNextPage = result.hasNextPage; + componentParams.children = result.uris.length === 0 ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(ToolChildrenWrapper, { children: [ + result.uris.map( + (uri, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ListableToolItem, + { + name: getBasename(uri.fsPath), + className: "void-w-full void-overflow-auto", + onClick: () => { + voidOpenFileFn(uri, accessor); + } + }, + i + ) + ), + result.hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ListableToolItem, { name: `Results truncated.`, isSmall: true, className: "void-w-full void-overflow-auto" }) + ] }); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "search_in_file": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + const toolsService = accessor.get("IToolsService"); + const title = getTitle(toolMessage); + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") return null; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + const infoarr = []; + const uriStr = getRelative(params.uri, accessor); + if (uriStr) infoarr.push(uriStr); + if (params.isRegex) infoarr.push("Uses regex search"); + componentParams.info = infoarr.join("; "); + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.numResults = result.lines.length; + componentParams.children = result.lines.length === 0 ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { className: "void-bg-void-bg-3", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("pre", { className: "void-font-mono void-whitespace-pre", children: toolsService.stringOfResult["search_in_file"](params, result) }) }) }); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "read_lint_errors": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const title = getTitle(toolMessage); + const { uri } = toolMessage.params ?? {}; + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") return null; + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + componentParams.info = getRelative(uri, accessor); + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + if (result.lintErrors) + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(LintErrorChildren, { lintErrors: result.lintErrors }); + else + componentParams.children = `No lint errors found.`; + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + // --- + "create_file_or_folder": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + componentParams.info = getRelative(params.uri, accessor); + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } else if (toolMessage.type === "rejected") { + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + if (params) { + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } else if (toolMessage.type === "running_now") ; else if (toolMessage.type === "tool_request") ; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "delete_file_or_folder": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + toolMessage.params?.isFolder ?? false; + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + componentParams.info = getRelative(params.uri, accessor); + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } else if (toolMessage.type === "rejected") { + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + if (params) { + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } else if (toolMessage.type === "running_now") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } else if (toolMessage.type === "tool_request") { + const { result } = toolMessage; + componentParams.onClick = () => { + voidOpenFileFn(params.uri, accessor); + }; + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "rewrite_file": { + resultWrapper: (params) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(EditTool, { ...params, content: params.toolMessage.params.newContent }); + } + }, + "edit_file": { + resultWrapper: (params) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(EditTool, { ...params, content: params.toolMessage.params.searchReplaceBlocks }); + } + }, + // --- + "run_command": { + resultWrapper: (params) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CommandTool, { ...params, type: "run_command" }); + } + }, + "run_persistent_command": { + resultWrapper: (params) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CommandTool, { ...params, type: "run_persistent_command" }); + } + }, + "open_persistent_terminal": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + const terminalToolsService = accessor.get("ITerminalToolService"); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const title = getTitle(toolMessage); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") return null; + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + const relativePath = params.cwd ? getRelative(URI.file(params.cwd), accessor) : ""; + componentParams.info = relativePath ? `Running in ${relativePath}` : void 0; + if (toolMessage.type === "success") { + const { result } = toolMessage; + const { persistentTerminalId } = result; + componentParams.desc1 = persistentTerminalNameOfId(persistentTerminalId); + componentParams.onClick = () => terminalToolsService.focusPersistentTerminal(persistentTerminalId); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "kill_persistent_terminal": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("ICommandService"); + const terminalToolsService = accessor.get("ITerminalToolService"); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const title = getTitle(toolMessage); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") return null; + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + if (toolMessage.type === "success") { + const { persistentTerminalId } = params; + componentParams.desc1 = persistentTerminalNameOfId(persistentTerminalId); + componentParams.onClick = () => terminalToolsService.focusPersistentTerminal(persistentTerminalId); + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "web_search": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("IToolsService"); + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") { + const componentParams2 = { title, desc1, desc1Info, isError: false, icon, isRejected: false }; + componentParams2.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-2 void-text-sm void-text-void-fg-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconLoading, {}), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Searching the web..." }) + ] }) }); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams2 }); + } + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + if (toolMessage.type === "success") { + const { result } = toolMessage; + componentParams.numResults = result.results?.length || 0; + if (result.results && result.results.length > 0) { + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-space-y-3", children: result.results.map( + (r, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-border void-border-void-border-2 void-bg-void-bg-2 void-rounded void-p-3 hover:void-bg-void-bg-3 void-transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "a", + { + href: r.url, + target: "_blank", + rel: "noopener noreferrer", + className: "void-block void-group", + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-text-sm void-font-semibold void-text-blue-400 group-hover:void-text-blue-300 void-mb-1 void-line-clamp-2", children: r.title }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-text-xs void-text-void-fg-4 void-mb-2 void-truncate", children: r.url }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-text-sm void-text-void-fg-2 void-line-clamp-3", children: r.snippet }) + ] + } + ) }, i) + ) }) }); + } else { + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-text-sm void-text-void-fg-3", children: "No search results found." }) }); + } + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + }, + "browse_url": { + resultWrapper: ({ toolMessage }) => { + const accessor = useAccessor(); + accessor.get("IToolsService"); + const title = getTitle(toolMessage); + const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor); + const icon = null; + if (toolMessage.type === "tool_request") return null; + if (toolMessage.type === "running_now") { + const componentParams2 = { title, desc1, desc1Info, isError: false, icon, isRejected: false }; + componentParams2.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-2 void-text-sm void-text-void-fg-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconLoading, {}), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Fetching content from URL..." }) + ] }) }); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams2 }); + } + const isError = false; + const isRejected = toolMessage.type === "rejected"; + const { rawParams, params } = toolMessage; + const componentParams = { title, desc1, desc1Info, isError, icon, isRejected }; + if (toolMessage.type === "success") { + const { result } = toolMessage; + const urlStr = result.url || params.url; + componentParams.onClick = () => { + if (urlStr) { + window.open(urlStr, "_blank", "noopener,noreferrer"); + } + }; + componentParams.info = urlStr ? `Source: ${new URL(urlStr).hostname}` : void 0; + if (result.content) { + const contentPreview = result.content.length > 2e3 ? result.content.substring(0, 2e3) + "\n\n... (content truncated)" : result.content; + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-space-y-3", children: [ + result.title && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-text-lg void-font-semibold void-text-void-fg-1", children: result.title }), + result.metadata?.publishedDate && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-text-xs void-text-void-fg-4", children: [ + "Published: ", + result.metadata.publishedDate + ] }), + urlStr && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "a", + { + href: urlStr, + target: "_blank", + rel: "noopener noreferrer", + className: "void-text-sm void-text-blue-400 hover:void-text-blue-300 void-block void-truncate", + children: urlStr + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-text-sm void-text-void-fg-2 void-whitespace-pre-wrap void-max-h-96 void-overflow-y-auto void-border void-border-void-border-2 void-bg-void-bg-3 void-rounded void-p-3", children: contentPreview }) + ] }) }); + } else { + componentParams.children = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolChildrenWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-text-sm void-text-void-fg-3", children: "No content extracted from URL." }) }); + } + } else if (toolMessage.type === "tool_error") { + const { result } = toolMessage; + componentParams.bottomChildren = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BottomChildren, { title: "Error", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CodeChildren, { children: result }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolHeaderWrapper, { ...componentParams }); + } + } +}; +var Checkpoint = ({ message, threadId, messageIdx, isCheckpointGhost, threadIsRunning }) => { + const accessor = useAccessor(); + const chatThreadService = accessor.get("IChatThreadService"); + const streamState = useFullChatThreadsStreamState(); + const chatThreadsState = useChatThreadsState(); + const isRunning = useChatThreadsStreamState(threadId)?.isRunning; + const isDisabled = (0, import_react19.useMemo)(() => { + if (isRunning) return true; + return Object.values(streamState).some((threadState) => threadState?.isRunning); + }, [isRunning, streamState]); + const threadMessagesLength = chatThreadsState.allThreads[threadId]?.messages.length ?? 0; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: `void-flex void-items-center void-justify-center void-px-2 `, + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: ` void-text-xs void-text-void-fg-3 void-select-none ${isCheckpointGhost ? "void-opacity-50" : "void-opacity-100"} ${isDisabled ? "void-cursor-default" : "void-cursor-pointer"} `, + style: { position: "relative", display: "inline-block" }, + onClick: () => { + if (threadIsRunning) return; + if (isDisabled) return; + chatThreadService.jumpToCheckpointBeforeMessageIdx({ + threadId, + messageIdx, + jumpToUserModified: messageIdx === threadMessagesLength - 1 + }); + }, + ...isDisabled ? { + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": `Disabled ${isRunning ? "when running" : "because another thread is running"}`, + "data-tooltip-place": "top" + } : {}, + children: "Checkpoint" + } + ) + } + ); +}; +var PlanComponent = import_react19.default.memo(({ message, isCheckpointGhost, threadId, messageIdx }) => { + const accessor = useAccessor(); + const chatThreadService = accessor.get("IChatThreadService"); + const [expandedSteps, setExpandedSteps] = (0, import_react19.useState)(/* @__PURE__ */ new Set()); + const [isCollapsed, setIsCollapsed] = (0, import_react19.useState)(false); + const chatThreadsState = useChatThreadsState(); + const approvalState = message.approvalState || "pending"; + const isRunning = useChatThreadsStreamState(threadId)?.isRunning; + const isBusy = isRunning === "LLM" || isRunning === "tool" || isRunning === "preparing"; + const isIdleLike = isRunning === void 0 || isRunning === "idle"; + const thread = chatThreadsState.allThreads[threadId]; + const threadMessages = thread?.messages ?? []; + const toolMessagesMap = (0, import_react19.useMemo)(() => { + const map = /* @__PURE__ */ new Map(); + for (const msg of threadMessages) { + if (msg.role === "tool") { + const toolMsg = msg; + map.set(toolMsg.id, toolMsg); + } + } + return map; + }, [threadMessages]); + const totalSteps = message.steps.length; + const completedSteps = (0, import_react19.useMemo)( + () => message.steps.filter((s) => s.status === "succeeded" || s.status === "skipped").length, + [message.steps] + ); + const progressText = (0, import_react19.useMemo)( + () => `${completedSteps} of ${totalSteps} ${totalSteps === 1 ? "Step" : "Steps"} Completed`, + [completedSteps, totalSteps] + ); + const hasPausedSteps = (0, import_react19.useMemo)( + () => message.steps.some((s) => s.status === "paused"), + [message.steps] + ); + const getCheckmarkIcon = (status, isDisabled) => { + if (isDisabled) { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-w-5 void-h-5 void-rounded-full void-border-2 void-border-void-fg-4 void-flex void-items-center void-justify-center void-opacity-40" }); + } + switch (status) { + case "succeeded": + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-w-5 void-h-5 void-rounded-full void-border-2 void-border-green-500 void-bg-green-500/20 void-flex void-items-center void-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Check, { size: 12, className: "void-text-green-400", strokeWidth: 3 }) }); + case "failed": + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-w-5 void-h-5 void-rounded-full void-border-2 void-border-red-500 void-bg-red-500/20 void-flex void-items-center void-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(X, { size: 12, className: "void-text-red-400", strokeWidth: 3 }) }); + case "running": + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-w-5 void-h-5 void-rounded-full void-border-2 void-border-yellow-500 void-bg-yellow-500/20 void-flex void-items-center void-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CircleEllipsis, { size: 12, className: "void-text-yellow-400 void-animate-spin" }) }); + case "paused": + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-w-5 void-h-5 void-rounded-full void-border-2 void-border-orange-500 void-bg-orange-500/20 void-flex void-items-center void-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Dot, { size: 12, className: "void-text-orange-400" }) }); + case "skipped": + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-w-5 void-h-5 void-rounded-full void-border-2 void-border-gray-500 void-bg-gray-500/20 void-flex void-items-center void-justify-center void-opacity-60", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Ban, { size: 12, className: "void-text-gray-400" }) }); + default: + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-w-5 void-h-5 void-rounded-full void-border-2 void-border-void-fg-3 void-flex void-items-center void-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-w-1.5 void-h-1.5 void-rounded-full void-bg-void-fg-3 void-opacity-60" }) }); + } + }; + const toggleStepExpanded = (stepNumber) => { + setExpandedSteps((prev) => { + const next = new Set(prev); + if (next.has(stepNumber)) { + next.delete(stepNumber); + } else { + next.add(stepNumber); + } + return next; + }); + }; + const handleApprove = () => { + if (isCheckpointGhost || isBusy) return; + chatThreadService.approvePlan({ threadId, messageIdx }); + }; + const handleReject = () => { + if (isCheckpointGhost || isBusy) return; + chatThreadService.rejectPlan({ threadId, messageIdx }); + }; + const handleToggleStep = (stepNumber) => { + if (isCheckpointGhost || isBusy) return; + chatThreadService.toggleStepDisabled({ threadId, messageIdx, stepNumber }); + }; + const getStatusBadge = (status) => { + switch (status) { + case "running": + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-px-1.5 void-py-0.5 void-text-xs void-rounded void-bg-yellow-500/20 void-text-yellow-400 void-border void-border-yellow-500/30", children: "Running" }); + case "failed": + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-px-1.5 void-py-0.5 void-text-xs void-rounded void-bg-red-500/20 void-text-red-400 void-border void-border-red-500/30", children: "Failed" }); + case "paused": + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-px-1.5 void-py-0.5 void-text-xs void-rounded void-bg-orange-500/20 void-text-orange-400 void-border void-border-orange-500/30", children: "Paused" }); + case "skipped": + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-px-1.5 void-py-0.5 void-text-xs void-rounded void-bg-gray-500/20 void-text-gray-400 void-border void-border-gray-500/30", children: "Skipped" }); + default: + return null; + } + }; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${isCheckpointGhost ? "void-opacity-50 void-pointer-events-none" : ""} void-my-3`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-bg-void-bg-1 void-border void-border-void-border-1 void-rounded-lg void-overflow-hidden", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-px-4 void-py-3 void-border-b void-border-void-border-1 void-bg-void-bg-2/30", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-justify-between", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-2 void-flex-1 void-min-w-0", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + onClick: () => setIsCollapsed(!isCollapsed), + className: "void-flex-shrink-0 void-p-1 hover:void-bg-void-bg-2 void-rounded void-transition-colors", + disabled: isCheckpointGhost, + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ChevronRight, + { + size: 16, + className: `void-text-void-fg-3 void-transition-transform ${isCollapsed ? "" : "void-rotate-90"}` + } + ) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-2 void-flex-1 void-min-w-0", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h3", { className: "void-text-void-fg-1 void-font-medium void-text-sm void-truncate", children: message.summary }), + approvalState === "pending" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-px-2 void-py-0.5 void-text-xs void-rounded void-bg-blue-500/20 void-text-blue-400 void-border void-border-blue-500/30 void-flex-shrink-0", children: "Pending Approval" }), + approvalState === "executing" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "void-px-2 void-py-0.5 void-text-xs void-rounded void-bg-yellow-500/20 void-text-yellow-400 void-border void-border-yellow-500/30 void-flex void-items-center void-gap-1 void-flex-shrink-0", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CircleEllipsis, { size: 12, className: "void-animate-spin" }), + "Executing" + ] }), + approvalState === "completed" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "void-px-2 void-py-0.5 void-text-xs void-rounded void-bg-green-500/20 void-text-green-400 void-border void-border-green-500/30 void-flex void-items-center void-gap-1 void-flex-shrink-0", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Check, { size: 12 }), + "Completed" + ] }) + ] }) + ] }), + !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-3 void-flex-shrink-0", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-3 void-text-xs", "aria-live": "polite", children: progressText }), + approvalState === "pending" && isIdleLike && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + title: "Reject plan", + "aria-label": "Reject plan", + onClick: handleReject, + className: "void-px-3 void-py-1.5 void-text-xs void-rounded void-bg-red-500/10 void-text-red-400 void-border void-border-red-500/20 hover:void-bg-red-500/20 void-transition-colors focus:void-outline-none focus:void-ring-2 focus:void-ring-red-500/40", + children: "Reject" + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + title: "Approve and execute", + "aria-label": "Approve and execute plan", + onClick: handleApprove, + className: "void-px-3 void-py-1.5 void-text-xs void-rounded void-bg-green-500/10 void-text-green-400 void-border void-border-green-500/20 hover:void-bg-green-500/20 void-transition-colors focus:void-outline-none focus:void-ring-2 focus:void-ring-green-500/40", + children: "Approve & Execute" + } + ) + ] }), + approvalState === "executing" && isBusy && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + "aria-label": "Pause plan execution", + onClick: () => chatThreadService.pauseAgentExecution({ threadId }), + className: "void-px-3 void-py-1.5 void-text-xs void-rounded void-bg-orange-500/10 void-text-orange-400 void-border void-border-orange-500/20 hover:void-bg-orange-500/20 void-transition-colors focus:void-outline-none focus:void-ring-2 focus:void-ring-orange-500/40", + children: "Pause" + } + ), + hasPausedSteps && !isBusy && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + "aria-label": "Resume plan execution", + onClick: () => chatThreadService.resumeAgentExecution({ threadId }), + className: "void-px-3 void-py-1.5 void-text-xs void-rounded void-bg-green-500/10 void-text-green-400 void-border void-border-green-500/20 hover:void-bg-green-500/20 void-transition-colors focus:void-outline-none focus:void-ring-2 focus:void-ring-green-500/40", + children: "Resume" + } + ) + ] }) + ] }) }), + !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-py-2", children: message.steps.map((step, idx) => { + const isExpanded = expandedSteps.has(step.stepNumber); + const isDisabled = step.disabled; + const status = step.status || "queued"; + const hasDetails = step.tools || step.files || step.error || step.toolCalls; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + className: `void-flex void-items-start void-gap-3 void-px-4 void-py-2.5 hover:void-bg-void-bg-2/30 void-transition-colors ${isDisabled ? "void-opacity-50" : ""} ${status === "failed" ? "void-bg-red-500/5" : ""}`, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex-shrink-0 void-mt-0.5", children: getCheckmarkIcon(status, isDisabled) }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex-1 void-min-w-0", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-start void-justify-between void-gap-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `void-text-void-fg-1 void-text-sm void-flex-1 void-leading-relaxed ${isDisabled ? "void-line-through void-text-void-fg-3" : ""} ${status === "succeeded" ? "void-text-void-fg-2" : ""}`, children: step.description }), + getStatusBadge(status) + ] }), + (approvalState === "pending" || approvalState === "executing" && status === "failed") && !isCheckpointGhost && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-2 void-mt-2", children: [ + approvalState === "pending" && !isRunning && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + "aria-label": `${isDisabled ? "Enable" : "Disable"} step ${step.stepNumber}`, + onClick: () => handleToggleStep(step.stepNumber), + className: "void-px-2 void-py-0.5 void-text-xs void-rounded void-bg-void-bg-2 void-text-void-fg-2 hover:void-bg-void-bg-2/80 void-border void-border-void-border-1 void-transition-colors", + children: isDisabled ? "Enable" : "Disable" + } + ), + approvalState === "executing" && status === "failed" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + "aria-label": `Retry step ${step.stepNumber}`, + onClick: () => chatThreadService.retryStep({ threadId, messageIdx, stepNumber: step.stepNumber }), + className: "void-px-2 void-py-0.5 void-text-xs void-rounded void-bg-green-500/10 void-text-green-400 hover:void-bg-green-500/20 void-border void-border-green-500/20 void-transition-colors", + children: "Retry" + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + "aria-label": `Skip step ${step.stepNumber}`, + onClick: () => chatThreadService.skipStep({ threadId, messageIdx, stepNumber: step.stepNumber }), + className: "void-px-2 void-py-0.5 void-text-xs void-rounded void-bg-gray-500/10 void-text-gray-400 hover:void-bg-gray-500/20 void-border void-border-gray-500/20 void-transition-colors", + children: "Skip" + } + ), + step.checkpointIdx !== void 0 && step.checkpointIdx !== null && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + "aria-label": `Rollback step ${step.stepNumber}`, + onClick: () => { + if (confirm("Rollback to the checkpoint before this step?")) chatThreadService.rollbackToStep({ threadId, messageIdx, stepNumber: step.stepNumber }); + }, + className: "void-px-2 void-py-0.5 void-text-xs void-rounded void-bg-orange-500/10 void-text-orange-400 hover:void-bg-orange-500/20 void-border void-border-orange-500/20 void-transition-colors", + children: "Rollback" + } + ) + ] }) + ] }), + hasDetails && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "button", + { + onClick: () => toggleStepExpanded(step.stepNumber), + className: "void-mt-2 void-flex void-items-center void-gap-1 void-text-void-fg-3 hover:void-text-void-fg-2 void-text-xs void-transition-colors", + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ChevronRight, + { + size: 12, + className: `void-transition-transform ${isExpanded ? "void-rotate-90" : ""}` + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { children: [ + isExpanded ? "Hide" : "Show", + " details" + ] }) + ] + } + ), + isExpanded && hasDetails && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-mt-3 void-space-y-3 void-pt-3 void-border-t void-border-void-border-1", children: [ + step.tools && step.tools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-text-void-fg-3 void-text-xs void-mb-2 void-font-medium", children: "Expected Tools:" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex void-flex-wrap void-gap-1.5", children: step.tools.map( + (tool, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-px-2 void-py-0.5 void-bg-blue-500/10 void-text-blue-400 void-text-xs void-rounded void-border void-border-blue-500/20", children: tool }, `${step.stepNumber}-tool-${tool}-${i}`) + ) }) + ] }), + step.toolCalls && step.toolCalls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-text-void-fg-3 void-text-xs void-mb-2 void-font-medium void-flex void-items-center void-gap-2", children: [ + "Tool Calls Executed ", + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-inline-flex void-items-center void-justify-center void-rounded-full void-bg-void-bg-2 void-text-void-fg-3 void-text-[10px] void-px-1.5 void-py-0.5 void-border void-border-void-border-1", children: step.toolCalls.length }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-space-y-1.5", children: step.toolCalls.map((toolId, i) => { + const toolMsg = toolMessagesMap.get(toolId); + if (!toolMsg) return null; + const isSuccess = toolMsg.type === "success"; + const isError = toolMsg.type === "tool_error"; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `void-p-2 void-rounded void-border void-text-xs ${isSuccess ? "void-bg-green-500/10 void-border-green-500/20" : isError ? "void-bg-red-500/10 void-border-red-500/20" : "void-bg-blue-500/10 void-border-blue-500/20"}`, children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-justify-between void-mb-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-font-medium void-text-void-fg-1", children: toolMsg.name }), + isSuccess && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Check, { size: 12, className: "void-text-green-400" }), + isError && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(X, { size: 12, className: "void-text-red-400" }) + ] }), + isError && toolMsg.result && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-mt-1 void-text-red-400 void-text-xs", children: toolMsg.result }), + isSuccess && toolMsg.result && typeof toolMsg.result === "object" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("details", { className: "void-mt-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("summary", { className: "void-text-void-fg-3 void-cursor-pointer void-text-xs hover:void-text-void-fg-2", children: "View result" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("pre", { className: "void-mt-1 void-p-2 void-bg-void-bg-2 void-rounded void-text-xs void-overflow-auto void-max-h-32 void-border void-border-void-border-1", children: JSON.stringify(toolMsg.result, null, 2) }) + ] }), + isError && toolMsg.params && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("details", { className: "void-mt-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("summary", { className: "void-text-void-fg-3 void-cursor-pointer void-text-xs hover:void-text-void-fg-2", children: "View params" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("pre", { className: "void-mt-1 void-p-2 void-bg-void-bg-2 void-rounded void-text-xs void-overflow-auto void-max-h-32 void-border void-border-void-border-1", children: JSON.stringify(toolMsg.params, null, 2) }) + ] }) + ] }, toolId); + }) }) + ] }), + step.files && step.files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-text-void-fg-3 void-text-xs void-mb-2 void-font-medium", children: "Files Affected:" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex void-flex-wrap void-gap-1.5", children: step.files.map( + (file, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "void-px-2 void-py-0.5 void-bg-purple-500/10 void-text-purple-400 void-text-xs void-rounded void-border void-border-purple-500/20 void-flex void-items-center void-gap-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(File, { size: 12 }), + file.split("/").pop() + ] }, i) + ) }) + ] }), + step.error && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-p-2 void-bg-red-500/10 void-border void-border-red-500/20 void-rounded void-text-red-400 void-text-xs void-flex void-items-start void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TriangleAlert, { size: 14, className: "void-flex-shrink-0 void-mt-0.5" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: step.error }) + ] }), + step.startTime && step.endTime && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-text-void-fg-3 void-text-xs", children: [ + "Duration: ", + ((step.endTime - step.startTime) / 1e3).toFixed(1), + "s" + ] }), + step.checkpointIdx !== void 0 && step.checkpointIdx !== null && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-text-void-fg-3 void-text-xs", children: [ + "Checkpoint: #", + step.checkpointIdx + ] }) + ] }) + ] }) + ] + }, + step.stepNumber + ); + }) }) + ] }) }); +}, (prev, next) => { + return prev.message === next.message && prev.isCheckpointGhost === next.isCheckpointGhost && prev.threadId === next.threadId && prev.messageIdx === next.messageIdx; +}); +var ReviewComponent = ({ message, isCheckpointGhost }) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${isCheckpointGhost ? "void-opacity-50" : ""} void-my-2`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `void-border void-rounded-lg void-p-4 ${message.completed ? "void-bg-green-500/10 void-border-green-500/30" : "void-bg-amber-500/10 void-border-amber-500/30"}`, children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-justify-between void-mb-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-2", children: [ + message.completed ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Check, { className: "void-text-green-400", size: 18 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TriangleAlert, { className: "void-text-amber-400", size: 18 }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h3", { className: `void-font-semibold void-text-sm ${message.completed ? "void-text-green-300" : "void-text-amber-300"}`, children: message.completed ? "Review Complete" : "Review: Issues Found" }) + ] }), + (message.executionTime || message.stepsCompleted !== void 0) && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-text-xs void-text-void-fg-3", children: [ + message.executionTime && `${(message.executionTime / 1e3).toFixed(1)}s`, + message.stepsCompleted !== void 0 && message.stepsTotal !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "void-ml-2", children: [ + message.stepsCompleted, + "/", + message.stepsTotal, + " steps" + ] }) + ] }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "void-text-void-fg-2 void-text-sm void-mb-3", children: message.summary }), + message.filesChanged && message.filesChanged.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-mb-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h4", { className: "void-text-void-fg-2 void-text-xs void-font-semibold void-mb-2", children: "Files Changed:" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-space-y-1", children: message.filesChanged.map( + (file, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-2 void-text-xs", children: [ + file.changeType === "created" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CirclePlus, { className: "void-text-green-400", size: 12 }), + file.changeType === "modified" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Pencil, { className: "void-text-blue-400", size: 12 }), + file.changeType === "deleted" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(X, { className: "void-text-red-400", size: 12 }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-2", children: file.path }) + ] }, i) + ) }) + ] }), + message.issues && message.issues.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-space-y-2 void-mb-3", children: message.issues.map( + (issue, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `void-flex void-gap-2 void-text-sm void-p-2 void-rounded ${issue.severity === "error" ? "void-bg-red-500/10 void-border void-border-red-500/20" : issue.severity === "warning" ? "void-bg-amber-500/10 void-border void-border-amber-500/20" : "void-bg-blue-500/10 void-border void-border-blue-500/20"}`, children: [ + issue.severity === "error" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(X, { className: "void-text-red-400 void-flex-shrink-0 void-mt-0.5", size: 16 }) : issue.severity === "warning" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TriangleAlert, { className: "void-text-amber-400 void-flex-shrink-0 void-mt-0.5", size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Info, { className: "void-text-blue-400 void-flex-shrink-0 void-mt-0.5", size: 16 }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: `${issue.severity === "error" ? "void-text-red-300" : issue.severity === "warning" ? "void-text-amber-300" : "void-text-blue-300"}`, children: issue.message }), + issue.file && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("p", { className: "void-text-void-fg-3 void-text-xs void-mt-1 void-flex void-items-center void-gap-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(File, { size: 12 }), + issue.file + ] }) + ] }) + ] }, i) + ) }), + message.nextSteps && message.nextSteps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-mt-3 void-pt-3 void-border-t void-border-void-border-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "void-text-void-fg-3 void-text-xs void-mb-2 void-font-medium", children: "Recommended Next Steps:" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ul", { className: "void-space-y-1", children: message.nextSteps.map( + (step, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("li", { className: "void-text-void-fg-2 void-text-xs void-flex void-items-start void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-4 void-mt-1", children: "\u2022" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: step }) + ] }, i) + ) }) + ] }) + ] }) }); +}; +var ChatBubble = import_react19.default.memo((props) => { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(_ChatBubble, { ...props }) }); +}, (prev, next) => { + return prev.chatMessage === next.chatMessage && prev.messageIdx === next.messageIdx && prev.isCommitted === next.isCommitted && prev.chatIsRunning === next.chatIsRunning && prev.currCheckpointIdx === next.currCheckpointIdx && prev.threadId === next.threadId && prev._scrollToBottom === next._scrollToBottom; +}); +var _ChatBubble = import_react19.default.memo(({ threadId, chatMessage, currCheckpointIdx, isCommitted, messageIdx, chatIsRunning, _scrollToBottom }) => { + const role = chatMessage.role; + const isCheckpointGhost = messageIdx > (currCheckpointIdx ?? Infinity) && !chatIsRunning; + if (role === "user") { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + UserMessageComponent, + { + chatMessage, + isCheckpointGhost, + currCheckpointIdx, + messageIdx, + _scrollToBottom + } + ); + } else if (role === "assistant") { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + AssistantMessageComponent, + { + chatMessage, + isCheckpointGhost, + messageIdx, + isCommitted + } + ); + } else if (role === "tool") { + if (chatMessage.type === "invalid_params") { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${isCheckpointGhost ? "void-opacity-50" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(InvalidTool, { toolName: chatMessage.name, message: chatMessage.content, mcpServerName: chatMessage.mcpServerName }) }); + } + const toolName = chatMessage.name; + const isBuiltInTool = isABuiltinToolName(toolName); + const ToolResultWrapper = isBuiltInTool ? builtinToolNameToComponent[toolName]?.resultWrapper : MCPToolWrapper; + if (ToolResultWrapper) + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${isCheckpointGhost ? "void-opacity-50" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ToolResultWrapper, + { + toolMessage: chatMessage, + messageIdx, + threadId + } + ) }), + chatMessage.type === "tool_request" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${isCheckpointGhost ? "void-opacity-50 void-pointer-events-none" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToolRequestAcceptRejectButtons, { toolName: chatMessage.name }) }) : null + ] }); + return null; + } else if (role === "interrupted_streaming_tool") { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `${isCheckpointGhost ? "void-opacity-50" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CanceledTool, { toolName: chatMessage.name, mcpServerName: chatMessage.mcpServerName }) }); + } else if (role === "checkpoint") { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + Checkpoint, + { + threadId, + message: chatMessage, + messageIdx, + isCheckpointGhost, + threadIsRunning: !!chatIsRunning + } + ); + } else if (role === "plan") { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + PlanComponent, + { + message: chatMessage, + isCheckpointGhost, + threadId, + messageIdx + } + ); + } else if (role === "review") { + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ReviewComponent, + { + message: chatMessage, + isCheckpointGhost + } + ); + } +}, (prev, next) => { + return prev.chatMessage === next.chatMessage && prev.messageIdx === next.messageIdx && prev.isCommitted === next.isCommitted && prev.chatIsRunning === next.chatIsRunning && prev.currCheckpointIdx === next.currCheckpointIdx && prev.threadId === next.threadId && prev._scrollToBottom === next._scrollToBottom; +}); +var CommandBarInChat = () => { + const { stateOfURI: commandBarStateOfURI, sortedURIs: sortedCommandBarURIs } = useCommandBarState(); + const numFilesChanged = sortedCommandBarURIs.length; + const accessor = useAccessor(); + const editCodeService = accessor.get("IEditCodeService"); + accessor.get("ICommandService"); + const chatThreadsState = useChatThreadsState(); + const commandBarState = useCommandBarState(); + const chatThreadsStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId); + const [fileDetailsOpenedState, setFileDetailsOpenedState] = (0, import_react19.useState)("auto-closed"); + const isFileDetailsOpened = fileDetailsOpenedState === "auto-opened" || fileDetailsOpenedState === "user-opened"; + (0, import_react19.useEffect)(() => { + if (numFilesChanged === 0) { + setFileDetailsOpenedState("auto-closed"); + } + if (numFilesChanged > 0 && fileDetailsOpenedState !== "user-closed") { + setFileDetailsOpenedState("auto-opened"); + } + }, [fileDetailsOpenedState, setFileDetailsOpenedState, numFilesChanged]); + const isFinishedMakingThreadChanges = ( + // there are changed files + commandBarState.sortedURIs.length !== 0 && commandBarState.sortedURIs.every((uri) => !commandBarState.stateOfURI[uri.fsPath]?.isStreaming) + ); + const threadStatus = chatThreadsStreamState?.isRunning === "awaiting_user" ? { title: "Needs Approval", color: "yellow" } : chatThreadsStreamState?.isRunning === "LLM" || chatThreadsStreamState?.isRunning === "tool" || chatThreadsStreamState?.isRunning === "preparing" ? { title: chatThreadsStreamState?.isRunning === "preparing" ? "Preparing" : "Running", color: "orange" } : { title: "Done", color: "dark" }; + const threadStatusHTML = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(StatusIndicator, { className: "void-mx-1", indicatorColor: threadStatus.color, title: threadStatus.title }); + const numFilesChangedStr = numFilesChanged === 0 ? "No files with changes" : `${sortedCommandBarURIs.length} file${numFilesChanged === 1 ? "" : "s"} with changes`; + const acceptRejectAllButtons = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + className: `void-flex void-items-center void-gap-0.5 ${isFinishedMakingThreadChanges ? "" : "void-opacity-0 void-pointer-events-none"}`, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + IconShell1, + { + Icon: X, + onClick: () => { + sortedCommandBarURIs.forEach((uri) => { + editCodeService.acceptOrRejectAllDiffAreas({ + uri, + removeCtrlKs: true, + behavior: "reject", + _addToHistory: true + }); + }); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "top", + "data-tooltip-content": "Reject all" + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + IconShell1, + { + Icon: Check, + onClick: () => { + sortedCommandBarURIs.forEach((uri) => { + editCodeService.acceptOrRejectAllDiffAreas({ + uri, + removeCtrlKs: true, + behavior: "accept", + _addToHistory: true + }); + }); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "top", + "data-tooltip-content": "Accept all" + } + ) + ] + } + ); + const fileDetailsContent = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-px-2 void-gap-1 void-w-full void-overflow-y-auto", children: sortedCommandBarURIs.map((uri, i) => { + const basename = getBasename(uri.fsPath); + const { sortedDiffIds, isStreaming } = commandBarStateOfURI[uri.fsPath] ?? {}; + const isFinishedMakingFileChanges = !isStreaming; + const numDiffs = sortedDiffIds?.length || 0; + const fileStatus = isFinishedMakingFileChanges ? { title: "Done", color: "dark" } : { title: "Running", color: "orange" }; + const fileNameHTML = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: "void-flex void-items-center void-gap-1.5 void-text-void-fg-3 hover:void-brightness-125 void-transition-all void-duration-200 void-cursor-pointer", + onClick: () => voidOpenFileFn(uri, accessor), + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-3", children: basename }) + } + ); + const detailsContent = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex void-px-4", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "void-text-void-fg-3 void-opacity-80", children: [ + numDiffs, + " diff", + numDiffs !== 1 ? "s" : "" + ] }) }); + const acceptRejectButtons = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + className: `void-flex void-items-center void-gap-0.5 ${isFinishedMakingFileChanges ? "" : "void-opacity-0 void-pointer-events-none"} `, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + IconShell1, + { + Icon: X, + onClick: () => { + editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "reject", _addToHistory: true }); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "top", + "data-tooltip-content": "Reject file" + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + IconShell1, + { + Icon: Check, + onClick: () => { + editCodeService.acceptOrRejectAllDiffAreas({ uri, removeCtrlKs: true, behavior: "accept", _addToHistory: true }); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "top", + "data-tooltip-content": "Accept file" + } + ) + ] + } + ); + const fileStatusHTML = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(StatusIndicator, { className: "void-mx-1", indicatorColor: fileStatus.color, title: fileStatus.title }); + return ( + // name, details + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-justify-between void-items-center", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center", children: [ + fileNameHTML, + detailsContent + ] }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-items-center void-gap-2", children: [ + acceptRejectButtons, + fileStatusHTML + ] }) + ] }, i) + ); + }) }); + const fileDetailsButton = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "button", + { + className: `void-flex void-items-center void-gap-1 void-rounded ${numFilesChanged === 0 ? "void-cursor-pointer" : "void-cursor-pointer hover:void-brightness-125 void-transition-all void-duration-200"}`, + onClick: () => isFileDetailsOpened ? setFileDetailsOpenedState("user-closed") : setFileDetailsOpenedState("user-opened"), + type: "button", + disabled: numFilesChanged === 0, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "svg", + { + className: "void-transition-transform void-duration-200 void-size-3.5", + style: { + transform: isFileDetailsOpened ? "rotate(0deg)" : "rotate(180deg)", + transition: "transform 0.2s cubic-bezier(0.25, 0.1, 0.25, 1)" + }, + xmlns: "http://www.w3.org/2000/svg", + width: "16", + height: "16", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "2", + strokeLinecap: "round", + strokeLinejoin: "round", + children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("polyline", { points: "18 15 12 9 6 15" }) + } + ), + numFilesChangedStr + ] + } + ); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-px-2", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: ` void-select-none void-flex void-w-full void-rounded-t-lg void-bg-void-bg-3 void-text-void-fg-3 void-text-xs void-text-nowrap void-overflow-hidden void-transition-all void-duration-200 void-ease-in-out ${isFileDetailsOpened ? "void-max-h-24" : "void-max-h-0"} `, + children: fileDetailsContent + } + ) }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + className: ` void-select-none void-flex void-w-full void-rounded-t-lg void-bg-void-bg-3 void-text-void-fg-3 void-text-xs void-text-nowrap void-border-t void-border-l void-border-r void-border-zinc-300/10 void-px-2 void-py-1 void-justify-between `, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex void-gap-2 void-items-center", children: fileDetailsButton }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-flex void-gap-2 void-items-center", children: [ + acceptRejectAllButtons, + threadStatusHTML + ] }) + ] + } + ) + ] }); +}; +var EditToolSoFar = ({ toolCallSoFar }) => { + if (!isABuiltinToolName(toolCallSoFar.name)) return null; + const accessor = useAccessor(); + const uri = toolCallSoFar.rawParams.uri ? URI.file(toolCallSoFar.rawParams.uri) : void 0; + const title = titleOfBuiltinToolName[toolCallSoFar.name].proposed; + const uriDone = toolCallSoFar.doneParams.includes("uri"); + const desc1 = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "void-flex void-items-center", children: [ + uriDone ? getBasename(toolCallSoFar.rawParams["uri"] ?? "unknown") : `Generating`, + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconLoading, {}) + ] }); + const desc1OnClick = () => { + uri && voidOpenFileFn(uri, accessor); + }; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + ToolHeaderWrapper, + { + title, + desc1, + desc1OnClick, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + EditToolChildren, + { + uri, + code: toolCallSoFar.rawParams.search_replace_blocks ?? toolCallSoFar.rawParams.new_content ?? "", + type: "rewrite" + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconLoading, {}) + ] + } + ); +}; +var SidebarChat = () => { + const textAreaRef = (0, import_react19.useRef)(null); + const textAreaFnsRef = (0, import_react19.useRef)(null); + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); + const chatThreadsService = accessor.get("IChatThreadService"); + const settingsState = useSettingsState(); + const chatThreadsState = useChatThreadsState(); + const currentThread = chatThreadsService.getCurrentThread(); + const previousMessages = currentThread?.messages ?? []; + const selections = currentThread.state.stagingSelections; + const setSelections = (s) => { + chatThreadsService.setCurrentThreadState({ stagingSelections: s }); + }; + const currThreadStreamState = useChatThreadsStreamState(chatThreadsState.currentThreadId); + const isRunning = currThreadStreamState?.isRunning; + const latestError = currThreadStreamState?.error; + const { displayContentSoFar, toolCallSoFar, reasoningSoFar } = currThreadStreamState?.llmInfo ?? {}; + const toolIsGenerating = toolCallSoFar && !toolCallSoFar.isDone; + const [instructionsAreEmpty, setInstructionsAreEmpty] = (0, import_react19.useState)(true); + const { + attachments: imageAttachments, + addImages: addImagesRaw, + removeImage, + retryImage, + cancelImage, + clearAll: clearImages, + focusedIndex: focusedImageIndex, + setFocusedIndex: setFocusedImageIndex, + validationError: imageValidationError + } = useImageAttachments(); + const { + attachments: pdfAttachments, + addPDFs: addPDFsRaw, + removePDF, + retryPDF, + cancelPDF, + clearAll: clearPDFs, + focusedIndex: focusedPDFIndex, + setFocusedIndex: setFocusedPDFIndex, + validationError: pdfValidationError + } = usePDFAttachments(); + const addPDFs = (0, import_react19.useCallback)(async (files) => { + const currentModelSel = settingsState.modelSelectionOfFeature["Chat"]; + if (currentModelSel?.providerName === "auto" && currentModelSel?.modelName === "auto") { + await addPDFsRaw(files); + return; + } + await addPDFsRaw(files); + }, [addPDFsRaw, settingsState]); + const addImages = (0, import_react19.useCallback)(async (files) => { + const currentModelSel = settingsState.modelSelectionOfFeature["Chat"]; + if (currentModelSel?.providerName === "auto" && currentModelSel?.modelName === "auto") { + await addImagesRaw(files); + return; + } + const { isSelectedModelVisionCapable, checkOllamaModelVisionCapable, hasVisionCapableApiKey, hasOllamaVisionModel, isOllamaAccessible } = await import('./visionModelHelper-N2YMBODM.js'); + let selectedIsVision = isSelectedModelVisionCapable(currentModelSel, settingsState.settingsOfProvider); + if (!selectedIsVision && currentModelSel?.providerName === "ollama") { + const ollamaAccessible2 = await isOllamaAccessible(); + if (ollamaAccessible2) { + selectedIsVision = await checkOllamaModelVisionCapable(currentModelSel.modelName); + } + } + if (selectedIsVision) { + await addImagesRaw(files); + return; + } + const hasApiKey = hasVisionCapableApiKey(settingsState.settingsOfProvider, currentModelSel); + const ollamaAccessible = await isOllamaAccessible(); + const hasOllamaVision = ollamaAccessible && await hasOllamaVisionModel(); + if (!hasApiKey && !hasOllamaVision) { + const notificationService2 = accessor.get("INotificationService"); + const commandService2 = accessor.get("ICommandService"); + notificationService2.notify({ + severity: 2, + // Severity.Warning + message: "No vision-capable models available. Please set up an API key (Anthropic, OpenAI, or Gemini) or install an Ollama vision model (e.g., llava, bakllava).", + actions: { + primary: [{ + id: "void.vision.setup", + label: "Setup Ollama Vision Models", + tooltip: "", + class: void 0, + enabled: true, + run: () => commandService2.executeCommand(CORTEXIDE_OPEN_SETTINGS_ACTION_ID) + }] + } + }); + return; + } + await addImagesRaw(files); + }, [addImagesRaw, settingsState, accessor]); + const isDisabled = instructionsAreEmpty && imageAttachments.length === 0 && pdfAttachments.length === 0 || !!isFeatureNameDisabled$1("Chat", settingsState); + const sidebarRef = (0, import_react19.useRef)(null); + const scrollContainerRef = (0, import_react19.useRef)(null); + const scrollToBottomCallback = (0, import_react19.useCallback)(() => { + scrollToBottom(scrollContainerRef); + }, [scrollContainerRef]); + const onSubmit = (0, import_react19.useCallback)(async (_forceSubmit) => { + if (isDisabled && !_forceSubmit) return; + if (isRunning) return; + const threadId2 = currentThread.id; + const userMessage = _forceSubmit || textAreaRef.current?.value || ""; + try { + const toolsService = accessor.get("IToolsService"); + const workspaceService = accessor.get("IWorkspaceContextService"); + const editorService = accessor.get("IEditorService"); + const languageService = accessor.get("ILanguageService"); + const historyService = accessor.get("IHistoryService"); + const notificationService2 = accessor.get("INotificationService"); + let outlineService = void 0; + try { + outlineService = accessor.get("IOutlineModelService"); + } catch { + } + const existing = /* @__PURE__ */ new Set(); + const existingSelections = chatThreadsState.allThreads[currentThread.id]?.state?.stagingSelections || []; + for (const s of existingSelections) existing.add(s.uri?.fsPath || ""); + const addFileSelection = async (uri) => { + if (!uri) return; + const key = uri.fsPath || uri.path || ""; + if (key && existing.has(key)) return; + existing.add(key); + const newSel = { + type: "File", + uri, + language: languageService.guessLanguageIdByFilepathOrFirstLine(uri) || "", + state: { wasAddedAsCurrentFile: false } + }; + await chatThreadsService.addNewStagingSelection(newSel); + }; + const addFolderSelection = async (uri) => { + if (!uri) return; + const key = uri.fsPath || uri.path || ""; + if (key && existing.has(key)) return; + existing.add(key); + const newSel = { + type: "Folder", + uri, + language: void 0, + state: void 0 + }; + await chatThreadsService.addNewStagingSelection(newSel); + }; + const tokens = []; + { + const quoted = [...userMessage.matchAll(/@"([^"]+)"/g)].map((m) => m[1]); + tokens.push(...quoted); + for (const m of userMessage.matchAll(/@([\w\.\-_/]+(?::\d+(?:-\d+)?)?)/g)) { + const t = m[1]; + if (t) tokens.push(t); + } + } + const special = /* @__PURE__ */ new Set(["selection", "workspace", "recent", "folder"]); + const unresolvedRefs = []; + for (const raw of tokens) { + if (raw === "selection") { + const active = editorService.activeTextEditorControl; + const activeResource = editorService.activeEditor?.resource; + const sel = active?.getSelection?.(); + if (activeResource && sel && !sel.isEmpty()) { + const newSel = { + type: "File", + uri: activeResource, + language: languageService.guessLanguageIdByFilepathOrFirstLine(activeResource) || "", + state: { wasAddedAsCurrentFile: false }, + range: sel + }; + const key = activeResource.fsPath || ""; + if (!existing.has(key)) { + existing.add(key); + await chatThreadsService.addNewStagingSelection(newSel); + } + } else { + unresolvedRefs.push("@selection (no active selection)"); + } + continue; + } + if (raw === "workspace") { + for (const folder of workspaceService.getWorkspace().folders) { + await addFolderSelection(folder.uri); + } + continue; + } + if (raw === "recent") { + for (const h of historyService.getHistory()) { + if (h.resource) await addFileSelection(h.resource); + } + continue; + } + if (raw.startsWith("sym:") || raw.startsWith("symbol:")) { + const symName = raw.replace(/^symbol?:/, ""); + let symbolFound = false; + if (outlineService && typeof outlineService.getCachedModels === "function") { + try { + const models = outlineService.getCachedModels(); + for (const om of models) { + const list2 = typeof om.asListOfDocumentSymbols === "function" ? om.asListOfDocumentSymbols() : []; + for (const s of list2) { + if ((s?.name || "").toLowerCase() === symName.toLowerCase()) { + symbolFound = true; + const uri = om.uri; + const range = s.range; + const key = uri?.fsPath || ""; + if (!existing.has(key)) { + existing.add(key); + await chatThreadsService.addNewStagingSelection({ + type: "File", + uri, + language: languageService.guessLanguageIdByFilepathOrFirstLine(uri) || "", + state: { wasAddedAsCurrentFile: false }, + range + }); + } + } + } + } + } catch (err) { + console.warn("Error resolving symbol:", err); + } + } + if (!symbolFound) { + unresolvedRefs.push(`@${raw} (symbol not found)`); + } + continue; + } + let query = raw; + let isFolderHint = false; + if (raw.startsWith("folder:")) { + isFolderHint = true; + query = raw.slice("folder:".length); + } + let resolved = false; + try { + const res = await (await toolsService.callTool.search_pathnames_only({ query, includePattern: null, pageNumber: 1 })).result; + const [first] = res.uris || []; + if (first) { + resolved = true; + if (isFolderHint) await addFolderSelection(first); + else + await addFileSelection(first); + } + } catch (err) { + console.warn("Error resolving reference:", err); + } + if (!resolved) { + unresolvedRefs.push(`@${raw}`); + } + } + if (unresolvedRefs.length > 0) { + const refList = unresolvedRefs.slice(0, 3).join(", "); + const moreText = unresolvedRefs.length > 3 ? ` and ${unresolvedRefs.length - 3} more` : ""; + notificationService2.warn(`Could not resolve reference${unresolvedRefs.length > 1 ? "s" : ""}: ${refList}${moreText}. Please check the file path or symbol name.`); + } + } catch (err) { + console.warn("Error resolving @references:", err); + } + const images = imageAttachments.filter((att) => att.uploadStatus === "success" || !att.uploadStatus).map((att) => ({ + id: att.id, + data: att.data, + mimeType: att.mimeType, + filename: att.filename, + width: att.width, + height: att.height, + size: att.size + })); + const processingPDFs = pdfAttachments.filter( + (att) => att.uploadStatus === "uploading" || att.uploadStatus === "processing" + ); + if (processingPDFs.length > 0) { + const processingNames = processingPDFs.map((p) => p.filename).join(", "); + notificationService.warn(`Some PDFs are still processing: ${processingNames}. They will be sent but may not have extracted text available yet.`); + } + const pdfs = pdfAttachments.filter((att) => att.uploadStatus !== "failed").map((att) => ({ + id: att.id, + data: att.data, + filename: att.filename, + size: att.size, + pageCount: att.pageCount, + selectedPages: att.selectedPages, + extractedText: att.extractedText, + pagePreviews: att.pagePreviews + })); + const currentModelSel = settingsState.modelSelectionOfFeature["Chat"]; + if ((images.length > 0 || pdfs.length > 0) && currentModelSel) { + const { isSelectedModelVisionCapable, checkOllamaModelVisionCapable, hasVisionCapableApiKey, hasOllamaVisionModel, isOllamaAccessible } = await import('./visionModelHelper-N2YMBODM.js'); + if (currentModelSel.providerName === "auto" && currentModelSel.modelName === "auto") { + if (images.length > 0) { + const hasApiKey = hasVisionCapableApiKey(settingsState.settingsOfProvider, currentModelSel); + const ollamaAccessible = await isOllamaAccessible(); + const hasOllamaVision = ollamaAccessible && await hasOllamaVisionModel(); + if (!hasApiKey && !hasOllamaVision) { + notificationService.error("No vision-capable models available. Please set up an API key (Anthropic, OpenAI, or Gemini) or install an Ollama vision model (e.g., llava, bakllava) to use images."); + return; + } + } + } else { + let isVisionCapable = isSelectedModelVisionCapable(currentModelSel, settingsState.settingsOfProvider); + if (!isVisionCapable && currentModelSel.providerName === "ollama") { + const ollamaAccessible = await isOllamaAccessible(); + if (ollamaAccessible) { + isVisionCapable = await checkOllamaModelVisionCapable(currentModelSel.modelName); + } + } + if (!isVisionCapable) { + const hasApiKey = hasVisionCapableApiKey(settingsState.settingsOfProvider, currentModelSel); + const ollamaAccessible = await isOllamaAccessible(); + const hasOllamaVision = ollamaAccessible && await hasOllamaVisionModel(); + if (!hasApiKey && !hasOllamaVision) { + notificationService.error("The selected model does not support images or PDFs. Please select a vision-capable model (e.g., Claude, GPT-4, Gemini, or an Ollama vision model like llava)."); + return; + } else { + notificationService.warn("The selected model may not support images or PDFs. Consider switching to a vision-capable model for better results."); + } + } + } + } + const stagingSelections = chatThreadsState.allThreads[currentThread.id]?.state?.stagingSelections || []; + setSelections([]); + if (textAreaFnsRef.current) { + textAreaFnsRef.current.setValue(""); + } + clearImages(); + clearPDFs(); + textAreaRef.current?.focus(); + try { + await chatThreadsService.addUserMessageAndStreamResponse({ userMessage, threadId: threadId2, images, pdfs, _chatSelections: stagingSelections }); + } catch (e) { + console.error("Error while sending message in chat:", e); + } + }, [chatThreadsService, isDisabled, isRunning, textAreaRef, textAreaFnsRef, setSelections, settingsState, imageAttachments, pdfAttachments, clearImages, clearPDFs, currentThread.id]); + const onAbort = async () => { + const threadId2 = currentThread.id; + await chatThreadsService.abortRunning(threadId2); + }; + accessor.get("IKeybindingService").lookupKeybinding(CORTEXIDE_CTRL_L_ACTION_ID)?.getLabel(); + const threadId = currentThread.id; + const currCheckpointIdx = chatThreadsState.allThreads[threadId]?.state?.currCheckpointIdx ?? void 0; + const mountedInfo = chatThreadsState.allThreads[threadId]?.state.mountedInfo; + const isResolved = mountedInfo?.mountedIsResolvedRef.current; + (0, import_react19.useEffect)(() => { + if (isResolved) return; + mountedInfo?._whenMountedResolver?.({ + textAreaRef, + scrollToBottom: scrollToBottomCallback + }); + }, [threadId, textAreaRef, scrollContainerRef, isResolved, mountedInfo, scrollToBottomCallback]); + const previousMessagesHTML = (0, import_react19.useMemo)(() => { + return previousMessages.map((message, i) => { + const messageKey = message.id || `msg-${i}`; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ChatBubble, + { + currCheckpointIdx, + chatMessage: message, + messageIdx: i, + isCommitted: true, + chatIsRunning: isRunning, + threadId, + _scrollToBottom: scrollToBottomCallback + }, + messageKey + ); + }); + }, [previousMessages, threadId, currCheckpointIdx, isRunning, scrollToBottomCallback]); + const streamingChatIdx = previousMessagesHTML.length; + const streamingChatMessage = (0, import_react19.useMemo)(() => ({ + role: "assistant", + displayContent: displayContentSoFar ?? "", + reasoning: reasoningSoFar ?? "", + anthropicReasoning: null + }), [displayContentSoFar, reasoningSoFar]); + const isActivelyStreaming = isRunning === "LLM" || isRunning === "preparing"; + const currStreamingMessageHTML = isActivelyStreaming && (reasoningSoFar || displayContentSoFar) ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ChatBubble, + { + currCheckpointIdx, + chatMessage: streamingChatMessage, + messageIdx: streamingChatIdx, + isCommitted: false, + chatIsRunning: isRunning, + threadId, + _scrollToBottom: null + }, + "curr-streaming-msg" + ) : null; + const generatingTool = toolIsGenerating ? toolCallSoFar.name === "edit_file" || toolCallSoFar.name === "rewrite_file" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + EditToolSoFar, + { + toolCallSoFar + }, + "curr-streaming-tool" + ) : null : null; + const messagesHTML = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + ScrollToBottomContainer, + { + scrollContainerRef, + className: ` void-flex void-flex-col void-px-3 void-py-3 void-space-y-3 void-w-full void-h-full void-overflow-x-hidden void-overflow-y-auto ${previousMessagesHTML.length === 0 && !displayContentSoFar ? "void-hidden" : ""} `, + children: [ + previousMessagesHTML, + currStreamingMessageHTML, + generatingTool, + isRunning === "LLM" || isRunning === "preparing" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ProseWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + IconLoading, + { + className: "void-opacity-50 void-text-sm", + showTokenCount: ( + // Only show token count when actively streaming (LLM) + // When isRunning is 'idle' or undefined, the message is complete and token count should stop + displayContentSoFar && isRunning === "LLM" ? Math.ceil(displayContentSoFar.length / 4) : void 0 + ) + } + ) }) : null, + latestError === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-px-2 void-my-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ErrorDisplay, + { + message: latestError.message, + fullError: latestError.fullError, + onDismiss: () => { + chatThreadsService.dismissStreamError(currentThread.id); + }, + showDismiss: true + } + ), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(WarningBox, { className: "void-text-sm void-my-1 void-mx-3", onClick: () => { + commandService.executeCommand(CORTEXIDE_OPEN_SETTINGS_ACTION_ID); + }, text: "Open settings" }) + ] }) + ] + }, + "messages" + chatThreadsState.currentThreadId + ); + const onChangeText = (0, import_react19.useCallback)((newStr) => { + setInstructionsAreEmpty(!newStr); + }, [setInstructionsAreEmpty]); + const onKeyDown = (0, import_react19.useCallback)((e) => { + if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) { + onSubmit(); + } else if (e.key === "Escape" && isRunning) { + onAbort(); + } + }, [onSubmit, onAbort, isRunning]); + const [ctxWarned, setCtxWarned] = (0, import_react19.useState)(false); + const estimateTokens = (0, import_react19.useCallback)((s) => Math.ceil((s || "").length / 4), []); + const modelSel = settingsState.modelSelectionOfFeature["Chat"]; + const { contextBudget, messagesTokens } = (0, import_react19.useMemo)(() => { + let budget = 0; + let tokens = 0; + if (modelSel && isValidProviderModelSelection(modelSel)) { + const { providerName, modelName } = modelSel; + const caps = getModelCapabilities(providerName, modelName, settingsState.overridesOfModel); + const contextWindow = caps.contextWindow; + const msOpts = settingsState.optionsOfModelSelection["Chat"][providerName]?.[modelName]; + const isReasoningEnabled2 = getIsReasoningEnabledState("Chat", providerName, modelName, msOpts, settingsState.overridesOfModel); + const rot = getReservedOutputTokenSpace(providerName, modelName, { isReasoningEnabled: isReasoningEnabled2, overridesOfModel: settingsState.overridesOfModel }) || 0; + budget = Math.max(256, Math.floor(contextWindow * 0.8) - rot); + tokens = previousMessages.reduce((acc, m) => { + if (m.role === "user") return acc + estimateTokens(m.content || ""); + if (m.role === "assistant") return acc + estimateTokens(m.displayContent || m.content || "" || ""); + return acc; + }, 0); + } + return { contextBudget: budget, messagesTokens: tokens }; + }, [modelSel, previousMessages, settingsState.overridesOfModel, estimateTokens]); + const draftTokens = estimateTokens(textAreaRef.current?.value || ""); + const contextTotal = messagesTokens + draftTokens; + const contextPct = contextBudget > 0 ? contextTotal / contextBudget : 0; + (0, import_react19.useEffect)(() => { + if (contextPct > 0.8 && contextPct < 1 && !ctxWarned) { + try { + accessor.get("INotificationService").info(`Context nearing limit: ~${contextTotal} / ${contextBudget} tokens. Older messages may be summarized.`); + } catch { + } + setCtxWarned(true); + } + if (contextPct < 0.6 && ctxWarned) setCtxWarned(false); + }, [contextPct, ctxWarned, contextTotal, contextBudget, accessor]); + const inputChatArea = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + VoidChatArea, + { + featureName: "Chat", + onSubmit: () => onSubmit(), + onAbort, + isStreaming: !!isRunning, + isDisabled, + showSelections: true, + selections, + setSelections, + onClickAnywhere: () => { + textAreaRef.current?.focus(); + }, + imageAttachments: imageAttachments.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + ImageAttachmentList, + { + attachments: imageAttachments, + onRemove: removeImage, + onRetry: retryImage, + onCancel: cancelImage, + focusedIndex: focusedImageIndex, + onFocusChange: setFocusedImageIndex + } + ), + imageValidationError && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-px-2 void-py-1 void-text-xs void-text-red-500 void-bg-red-500/10 void-border void-border-red-500/20 void-rounded-md void-mx-2", children: imageValidationError.message }) + ] }) : null, + onImagePaste: addImages, + onImageDrop: addImages, + onPDFDrop: addPDFs, + pdfAttachments: pdfAttachments.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + PDFAttachmentList, + { + attachments: pdfAttachments, + onRemove: removePDF, + onRetry: retryPDF, + onCancel: cancelPDF, + focusedIndex: focusedPDFIndex, + onFocusChange: setFocusedPDFIndex + } + ), + pdfValidationError && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-px-2 void-py-1 void-text-xs void-text-red-500 void-bg-red-500/10 void-border void-border-red-500/20 void-rounded-md void-mx-2", children: pdfValidationError }) + ] }) : null, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + VoidInputBox2, + { + enableAtToMention: true, + appearance: "chatDark", + className: `void-min-h-[60px] void-px-3 void-py-3 void-rounded-2xl`, + placeholder: "Plan, @ for context", + onChangeText, + onKeyDown, + onFocus: () => { + chatThreadsService.setCurrentlyFocusedMessageIdx(void 0); + }, + ref: textAreaRef, + fnsRef: textAreaFnsRef, + multiline: true + } + ), + selections.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-mt-1 void-flex void-flex-wrap void-gap-1 void-px-1", children: selections.map((sel, idx) => { + const name = sel.type === "Folder" ? sel.uri?.path?.split("/").filter(Boolean).pop() || "folder" : sel.uri?.path?.split("/").pop() || "file"; + const fullPath = sel.uri?.fsPath || sel.uri?.path || name; + const rangeLabel = sel.range ? ` \u2022 ${sel.range.startLineNumber}-${sel.range.endLineNumber}` : ""; + const tooltipText = sel.range ? `${fullPath} (lines ${sel.range.startLineNumber}-${sel.range.endLineNumber})` : fullPath; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "span", + { + className: "void-inline-flex void-items-center void-gap-1 void-px-2 void-py-0.5 void-rounded void-border void-border-void-border-3 void-bg-void-bg-1 void-text-void-fg-2 void-text-[11px]", + title: tooltipText, + "aria-label": tooltipText, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-opacity-80", children: sel.type === "Folder" ? "Folder" : "File" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-1", children: name }), + rangeLabel && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-opacity-70", children: rangeLabel }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "button", + { + className: "void-ml-1 void-text-void-fg-3 hover:void-text-void-fg-1", + onClick: () => { + chatThreadsService.popStagingSelections(1); + }, + "aria-label": `Remove ${name}`, + children: "\xD7" + } + ) + ] + }, + idx + ); + }) }) + ] + } + ); + const isLandingPage = previousMessages.length === 0; + const initiallySuggestedPromptsHTML = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-flex void-flex-col void-gap-2 void-w-full void-text-nowrap void-text-void-fg-3 void-select-none", children: [ + "Summarize my codebase", + "How do types work in Rust?", + "Create a .voidrules file for me" + ].map( + (text, index3) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + "div", + { + className: "void-py-1 void-px-2 void-rounded void-text-sm void-bg-zinc-700/5 hover:void-bg-zinc-700/10 dark:void-bg-zinc-300/5 dark:hover:void-bg-zinc-300/10 void-cursor-pointer void-opacity-80 hover:void-opacity-100", + onClick: () => onSubmit(text), + children: text + }, + index3 + ) + ) }); + const threadPageInput = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-px-4", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CommandBarInChat, {}) }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-px-2 void-pb-2", children: [ + inputChatArea, + modelSel ? (() => { + const pctNum = Math.max(0, Math.min(100, Math.round(contextPct * 100))); + const color = contextPct >= 1 ? "text-red-500" : contextPct > 0.8 ? "text-amber-500" : "text-void-fg-3"; + const barColor = contextPct >= 1 ? "bg-red-500" : contextPct > 0.8 ? "bg-amber-500" : "bg-void-fg-3/60"; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-mt-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `void-text-[10px] ${color}`, children: [ + "Context ~", + contextTotal, + " / ", + contextBudget, + " tokens (", + pctNum, + "%)" + ] }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-h-[3px] void-w-full void-bg-void-border-3 void-rounded void-mt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `void-h-[3px] ${barColor} void-rounded`, style: { width: `${pctNum}%` }, "aria-label": `Context usage ${pctNum}%` }) }) + ] }); + })() : null + ] }) + ] }, "input" + chatThreadsState.currentThreadId); + const landingPageInput = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-pt-8", children: [ + inputChatArea, + modelSel ? (() => { + const pctNum = Math.max(0, Math.min(100, Math.round(contextPct * 100))); + const color = contextPct >= 1 ? "text-red-500" : contextPct > 0.8 ? "text-amber-500" : "text-void-fg-3"; + const barColor = contextPct >= 1 ? "bg-red-500" : contextPct > 0.8 ? "bg-amber-500" : "bg-void-fg-3/60"; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-mt-1 void-px-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `void-text-[10px] ${color}`, children: [ + "Context ~", + contextTotal, + " / ", + contextBudget, + " tokens (", + pctNum, + "%)" + ] }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-h-[3px] void-w-full void-bg-void-border-3 void-rounded void-mt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `void-h-[3px] ${barColor} void-rounded`, style: { width: `${pctNum}%` }, "aria-label": `Context usage ${pctNum}%` }) }) + ] }); + })() : null + ] }) }); + const keybindingService = accessor.get("IKeybindingService"); + const quickActions = [ + { id: "void.explainCode", label: "Explain" }, + { id: "void.refactorCode", label: "Refactor" }, + { id: "void.addTests", label: "Add Tests" }, + { id: "void.fixTests", label: "Fix Tests" }, + { id: "void.writeDocstring", label: "Docstring" }, + { id: "void.optimizeCode", label: "Optimize" }, + { id: "void.debugCode", label: "Debug" } + ]; + const QuickActionsBar = () => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-w-full void-flex void-items-center void-justify-center void-gap-2 void-flex-wrap void-mt-3 void-select-none void-px-1", children: quickActions.map(({ id, label }) => { + const kb = keybindingService.lookupKeybinding(id)?.getLabel(); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "button", + { + className: "void-px-3 void-py-1.5 void-rounded-full void-bg-gradient-to-br void-from-[var(--cortex-surface-2)] void-via-[var(--cortex-surface-3)] void-to-[var(--cortex-surface-4)] void-border void-border-void-border-3 void-text-xs void-text-void-fg-1 void-shadow-[0_3px_12px_rgba(0,0,0,0.45)] hover:-void-translate-y-0.5 void-transition-all void-duration-150 void-ease-out void-void-focus-ring", + onClick: () => commandService.executeCommand(id), + title: kb ? `${label} (${kb})` : label, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: label }), + kb && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-ml-1 void-px-1 void-rounded void-bg-[var(--vscode-keybindingLabel-background)] void-text-[var(--vscode-keybindingLabel-foreground)] void-border void-border-[var(--vscode-keybindingLabel-border)]", children: kb }) + ] + }, + id + ); + }) }); + const ContextChipsBar = () => { + const editorService = accessor.get("IEditorService"); + const activeEditor = editorService?.activeEditor; + const activeResource = activeEditor?.resource; + const activeFileLabel = activeResource ? activeResource.path?.split("/").pop() : void 0; + const modelSel2 = settingsState.modelSelectionOfFeature["Chat"]; + const modelLabel = modelSel2 ? `${modelSel2.providerName}:${modelSel2.modelName}` : void 0; + if (!activeFileLabel && !modelLabel) return null; + return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-w-full void-flex void-items-center void-gap-2 void-flex-wrap void-mt-2 void-mb-1 void-px-1", children: [ + activeFileLabel && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "void-inline-flex void-items-center void-gap-1 void-px-2 void-py-0.5 void-rounded void-border void-border-void-border-3 void-bg-void-bg-1 void-text-void-fg-2 void-text-[11px]", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "File" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-1", children: activeFileLabel }) + ] }), + modelLabel && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "void-inline-flex void-items-center void-gap-1 void-px-2 void-py-0.5 void-rounded void-border void-border-void-border-3 void-bg-void-bg-1 void-text-void-fg-2 void-text-[11px]", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Model" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "void-text-void-fg-1", children: modelLabel }) + ] }) + ] }); + }; + const landingPageContent = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + ref: sidebarRef, + className: "void-w-full void-h-full void-max-h-full void-flex void-flex-col void-overflow-auto", + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ChatTabsBar, {}) }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "void-px-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ErrorBoundary_default, { children: landingPageInput }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ContextChipsBar, {}) }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(QuickActionsBar, {}) }), + Object.keys(chatThreadsState.allThreads).length > 1 ? ( + // show if there are threads + /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(ErrorBoundary_default, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-pt-6 void-mb-2 void-text-void-fg-3 void-text-root void-select-none void-pointer-events-none", children: "Previous Threads" }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PastThreadsList, {}) + ] }) + ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(ErrorBoundary_default, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "void-pt-6 void-mb-2 void-text-void-fg-3 void-text-root void-select-none void-pointer-events-none", children: "Suggestions" }), + initiallySuggestedPromptsHTML + ] }) + ] }) + ] + } + ); + const threadPageContent = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "div", + { + ref: sidebarRef, + className: "void-w-full void-h-full void-flex void-flex-col void-overflow-hidden", + children: [ + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ChatTabsBar, {}) }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ErrorBoundary_default, { children: messagesHTML }), + /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ErrorBoundary_default, { children: threadPageInput }) + ] + } + ); + return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)( + import_react19.Fragment, + { + children: isLandingPage ? landingPageContent : threadPageContent + }, + threadId + ); +}; + +// src2/void-settings-tsx/WarningBox.tsx +var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1); +var WarningBox = ({ text, onClick, className }) => { + return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)( + "div", + { + className: ` void-text-void-warning void-brightness-90 void-opacity-90 void-w-fit void-text-xs void-text-ellipsis ${onClick ? `hover:void-brightness-75 void-transition-all void-duration-200 void-cursor-pointer` : ""} void-flex void-items-center void-flex-nowrap ${className} `, + onClick, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime16.jsx)( + IconWarning, + { + size: 14, + className: "void-mr-1 void-flex-shrink-0" + } + ), + /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: text }) + ] + } + ); +}; + +// src2/sidebar-tsx/ErrorBoundary.tsx +var import_jsx_runtime17 = __toESM(require_jsx_runtime(), 1); +var ErrorBoundary = class extends import_react20.Component { + constructor(props) { + super(props); + this.state = { + hasError: false, + error: null, + errorInfo: null + }; + } + static getDerivedStateFromError(error2) { + return { + hasError: true, + error: error2 + }; + } + componentDidCatch(error2, errorInfo) { + this.setState({ + error: error2, + errorInfo + }); + } + render() { + if (this.state.hasError && this.state.error) { + if (this.props.fallback) { + return this.props.fallback; + } + return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(WarningBox, { text: this.state.error + "" }); + } + return this.props.children; + } +}; +var ErrorBoundary_default = ErrorBoundary; +var import_jsx_runtime18 = __toESM(require_jsx_runtime(), 1); +var ButtonLeftTextRightOption = ({ text, leftButton }) => { + return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-text-void-fg-3 void-px-3 void-py-0.5 void-rounded-sm void-overflow-hidden void-gap-2", children: [ + leftButton ? leftButton : null, + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: text }) + ] }); +}; +var RefreshModelButton = ({ providerName }) => { + const refreshModelState = useRefreshModelState(); + const accessor = useAccessor(); + const refreshModelService = accessor.get("IRefreshModelService"); + const metricsService = accessor.get("IMetricsService"); + const [justFinished, setJustFinished] = (0, import_react21.useState)(null); + useRefreshModelListener( + (0, import_react21.useCallback)((providerName2, refreshModelState2) => { + if (providerName2 !== providerName) return; + const { state: state2 } = refreshModelState2[providerName]; + if (!(state2 === "finished" || state2 === "error")) return; + setJustFinished(state2); + const tid = setTimeout(() => { + setJustFinished(null); + }, 2e3); + return () => clearTimeout(tid); + }, [providerName]) + ); + const { state } = refreshModelState[providerName]; + const { title: providerTitle } = displayInfoOfProviderName(providerName); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + ButtonLeftTextRightOption, + { + leftButton: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + className: "void-flex void-items-center", + disabled: state === "refreshing" || justFinished !== null, + onClick: () => { + refreshModelService.startRefreshingModels(providerName, { enableProviderOnSuccess: false, doNotFire: false }); + metricsService.capture("Click", { providerName, action: "Refresh Models" }); + }, + children: justFinished === "finished" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Check, { className: "void-stroke-green-500 void-size-3" }) : justFinished === "error" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(X, { className: "void-stroke-red-500 void-size-3" }) : state === "refreshing" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(LoaderCircle, { className: "void-size-3 void-animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RefreshCw, { className: "void-size-3" }) + } + ), + text: justFinished === "finished" ? `${providerTitle} Models are up-to-date!` : justFinished === "error" ? `${providerTitle} not found!` : `Manually refresh ${providerTitle} models.` + } + ); +}; +var RefreshableModels = () => { + const settingsState = useSettingsState(); + const buttons = refreshableProviderNames.map((providerName) => { + if (!settingsState.settingsOfProvider[providerName]._didFillInProviderSettings) return null; + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RefreshModelButton, { providerName }, providerName); + }); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children: buttons }); +}; +var RefreshRemoteCatalogButton = ({ providerName }) => { + const accessor = useAccessor(); + const refreshModelService = accessor.get("IRefreshModelService"); + const metricsService = accessor.get("IMetricsService"); + const [isRefreshing, setIsRefreshing] = (0, import_react21.useState)(false); + const [justFinished, setJustFinished] = (0, import_react21.useState)(null); + const { title: providerTitle } = displayInfoOfProviderName(providerName); + const handleRefresh = async () => { + if (isRefreshing) return; + setIsRefreshing(true); + setJustFinished(null); + try { + await refreshModelService.refreshRemoteCatalog(providerName, true); + setJustFinished("finished"); + metricsService.capture("Click", { providerName, action: "Refresh Remote Catalog" }); + } catch (error2) { + console.error("Failed to refresh remote catalog:", error2); + setJustFinished("error"); + } finally { + setIsRefreshing(false); + const tid = setTimeout(() => { + setJustFinished(null); + }, 2e3); + return () => clearTimeout(tid); + } + }; + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + ButtonLeftTextRightOption, + { + leftButton: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + className: "void-flex void-items-center", + disabled: isRefreshing || justFinished !== null, + onClick: handleRefresh, + children: justFinished === "finished" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Check, { className: "void-stroke-green-500 void-size-3" }) : justFinished === "error" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(X, { className: "void-stroke-red-500 void-size-3" }) : isRefreshing ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(LoaderCircle, { className: "void-size-3 void-animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RefreshCw, { className: "void-size-3" }) + } + ), + text: justFinished === "finished" ? `${providerTitle} catalog refreshed!` : justFinished === "error" ? `Failed to refresh ${providerTitle} catalog` : `Refresh ${providerTitle} model catalog` + } + ); +}; +var RefreshableRemoteCatalogs = () => { + const settingsState = useSettingsState(); + const buttons = nonlocalProviderNames.map((providerName) => { + if (!settingsState.settingsOfProvider[providerName]._didFillInProviderSettings) return null; + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RefreshRemoteCatalogButton, { providerName }, providerName); + }); + const validButtons = buttons.filter(Boolean); + if (validButtons.length === 0) return null; + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children: validButtons }); +}; +var AnimatedCheckmarkButton = ({ text, className }) => { + const [dashOffset, setDashOffset] = (0, import_react21.useState)(40); + (0, import_react21.useEffect)(() => { + const startTime = performance.now(); + const duration = 500; + const animate = (currentTime) => { + const elapsed = currentTime - startTime; + const progress = Math.min(elapsed / duration, 1); + const newOffset = 40 - progress * 40; + setDashOffset(newOffset); + if (progress < 1) { + requestAnimationFrame(animate); + } + }; + const animationId = requestAnimationFrame(animate); + return () => cancelAnimationFrame(animationId); + }, []); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)( + "div", + { + className: `void-flex void-items-center void-gap-1.5 void-w-fit ${className ? className : `void-px-2 void-py-0.5 void-text-xs void-text-zinc-900 void-bg-zinc-100 void-rounded-sm`} `, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("svg", { className: "void-size-4", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "path", + { + d: "M5 13l4 4L19 7", + stroke: "currentColor", + strokeWidth: "2", + strokeLinecap: "round", + strokeLinejoin: "round", + style: { + strokeDasharray: 40, + strokeDashoffset: dashOffset + } + } + ) }), + text + ] + } + ); +}; +var AddButton = ({ disabled, text = "Add", ...props }) => { + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + disabled, + className: `void-bg-[#0e70c0] void-px-3 void-py-1 void-text-white void-rounded-sm ${!disabled ? "hover:void-bg-[#1177cb] void-cursor-pointer" : "void-opacity-50 void-cursor-not-allowed void-bg-opacity-70"}`, + ...props, + children: text + } + ); +}; +var ConfirmButton = ({ children, onConfirm, className }) => { + const [confirm2, setConfirm] = (0, import_react21.useState)(false); + const ref = (0, import_react21.useRef)(null); + (0, import_react21.useEffect)(() => { + if (!confirm2) return; + const handleClickOutside = (e) => { + if (ref.current && !ref.current.contains(e.target)) { + setConfirm(false); + } + }; + document.addEventListener("click", handleClickOutside); + return () => document.removeEventListener("click", handleClickOutside); + }, [confirm2]); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref, className: `void-inline-block`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className, onClick: () => { + if (!confirm2) { + setConfirm(true); + } else { + onConfirm(); + setConfirm(false); + } + }, children: confirm2 ? `Confirm Reset` : children }) }); +}; +var SimpleModelSettingsDialog = ({ + isOpen, + onClose, + modelInfo +}) => { + if (!isOpen || !modelInfo) return null; + const { modelName, providerName, type } = modelInfo; + const accessor = useAccessor(); + const settingsState = useSettingsState(); + const mouseDownInsideModal = (0, import_react21.useRef)(false); + const settingsStateService = accessor.get("ICortexideSettingsService"); + const defaultModelCapabilities = getModelCapabilities(providerName, modelName, void 0); + const currentOverrides = settingsState.overridesOfModel?.[providerName]?.[modelName] ?? void 0; + const { recognizedModelName, isUnrecognizedModel } = defaultModelCapabilities; + const partialDefaults = {}; + for (const k of modelOverrideKeys) { + if (defaultModelCapabilities[k]) partialDefaults[k] = defaultModelCapabilities[k]; + } + const placeholder = JSON.stringify(partialDefaults, null, 2); + const [overrideEnabled, setOverrideEnabled] = (0, import_react21.useState)(() => !!currentOverrides); + const [errorMsg, setErrorMsg] = (0, import_react21.useState)(null); + const textAreaRef = (0, import_react21.useRef)(null); + (0, import_react21.useEffect)(() => { + if (!isOpen) return; + const cur = settingsState.overridesOfModel?.[providerName]?.[modelName]; + setOverrideEnabled(!!cur); + setErrorMsg(null); + }, [isOpen, providerName, modelName, settingsState.overridesOfModel, placeholder]); + const onSave = async () => { + if (!overrideEnabled) { + await settingsStateService.setOverridesOfModel(providerName, modelName, void 0); + onClose(); + return; + } + let parsedInput; + if (textAreaRef.current?.value) { + try { + parsedInput = JSON.parse(textAreaRef.current.value); + } catch (e) { + setErrorMsg("Invalid JSON"); + return; + } + } else { + setErrorMsg("Invalid JSON"); + return; + } + const cleaned = {}; + for (const k of modelOverrideKeys) { + if (!(k in parsedInput)) continue; + const isEmpty = parsedInput[k] === "" || parsedInput[k] === null || parsedInput[k] === void 0; + if (!isEmpty) { + cleaned[k] = parsedInput[k]; + } + } + await settingsStateService.setOverridesOfModel(providerName, modelName, cleaned); + onClose(); + }; + const sourcecodeOverridesLink = `https://github.com/opencortexide/cortexide/blob/main/src/vs/workbench/contrib/cortexide/common/modelCapabilities.ts#L146-L172`; + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "div", + { + className: "void-fixed void-inset-0 void-bg-black/50 void-flex void-items-center void-justify-center void-z-[9999999]", + onMouseDown: () => { + mouseDownInsideModal.current = false; + }, + onMouseUp: () => { + if (!mouseDownInsideModal.current) { + onClose(); + } + mouseDownInsideModal.current = false; + }, + children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)( + "div", + { + className: "void-bg-void-bg-1 void-rounded-md void-p-4 void-max-w-xl void-w-full void-shadow-xl void-overflow-y-auto void-max-h-[90vh]", + onClick: (e) => e.stopPropagation(), + onMouseDown: (e) => { + mouseDownInsideModal.current = true; + e.stopPropagation(); + }, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-justify-between void-items-center void-mb-4", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("h3", { className: "void-text-lg void-font-medium", children: [ + "Change Defaults for ", + modelName, + " (", + displayInfoOfProviderName(providerName).title, + ")" + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + onClick: onClose, + className: "void-text-void-fg-3 hover:void-text-void-fg-1", + children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(X, { className: "void-size-5" }) + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-sm void-text-void-fg-3 void-mb-4", children: type === "default" ? `${modelName} comes packaged with CortexIDE, so you shouldn't need to change these settings.` : isUnrecognizedModel ? `Model not recognized by CortexIDE.` : `CortexIDE recognizes ${modelName} ("${recognizedModelName}").` }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-2 void-mb-4", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidSwitch, { size: "xs", value: overrideEnabled, onChange: setOverrideEnabled }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-sm", children: "Override model defaults" }) + ] }), + overrideEnabled && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-sm void-text-void-fg-3 void-mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatMarkdownRender, { string: `See the [sourcecode](${sourcecodeOverridesLink}) for a reference on how to set this JSON (advanced).`, chatMessageLocation: void 0 }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "textarea", + { + ref: textAreaRef, + className: `void-w-full void-min-h-[200px] void-p-2 void-rounded-sm void-border void-border-void-border-2 void-bg-void-bg-2 void-resize-none void-font-mono void-text-sm ${!overrideEnabled ? "void-text-void-fg-3" : ""}`, + defaultValue: overrideEnabled && currentOverrides ? JSON.stringify(currentOverrides, null, 2) : placeholder, + placeholder, + readOnly: !overrideEnabled + }, + overrideEnabled + "" + ), + errorMsg && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-red-500 void-mt-2 void-text-sm", children: errorMsg }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-justify-end void-gap-2 void-mt-4", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { onClick: onClose, className: "void-px-3 void-py-1", children: "Cancel" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidButtonBgDarken, + { + onClick: onSave, + className: "void-px-3 void-py-1 void-bg-[#0e70c0] void-text-white", + children: "Save" + } + ) + ] }) + ] + } + ) + } + ); +}; +var ModelDump = ({ filteredProviders }) => { + const accessor = useAccessor(); + const settingsStateService = accessor.get("ICortexideSettingsService"); + const settingsState = useSettingsState(); + const [openSettingsModel, setOpenSettingsModel] = (0, import_react21.useState)(null); + const [isAddModelOpen, setIsAddModelOpen] = (0, import_react21.useState)(false); + const [showCheckmark, setShowCheckmark] = (0, import_react21.useState)(false); + const [userChosenProviderName, setUserChosenProviderName] = (0, import_react21.useState)(null); + const [modelName, setModelName] = (0, import_react21.useState)(""); + const [errorString, setErrorString] = (0, import_react21.useState)(""); + const modelDump = []; + const providersToShow = filteredProviders || providerNames; + for (let providerName of providersToShow) { + const providerSettings = settingsState.settingsOfProvider[providerName]; + modelDump.push(...providerSettings.models.map((model) => ({ ...model, providerName, providerEnabled: !!providerSettings._didFillInProviderSettings }))); + } + modelDump.sort((a, b) => { + return Number(b.providerEnabled) - Number(a.providerEnabled); + }); + const handleAddModel = () => { + if (!userChosenProviderName) { + setErrorString("Please select a provider."); + return; + } + if (!modelName) { + setErrorString("Please enter a model name."); + return; + } + if (settingsState.settingsOfProvider[userChosenProviderName].models.find((m) => m.modelName === modelName)) { + setErrorString(`This model already exists.`); + return; + } + settingsStateService.addModel(userChosenProviderName, modelName); + setShowCheckmark(true); + setTimeout(() => { + setShowCheckmark(false); + setIsAddModelOpen(false); + setUserChosenProviderName(null); + setModelName(""); + }, 1500); + setErrorString(""); + }; + return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "", children: [ + modelDump.map((m, i) => { + const { isHidden, type, modelName: modelName2, providerName, providerEnabled } = m; + const isNewProviderName = (i > 0 ? modelDump[i - 1] : void 0)?.providerName !== providerName; + const providerTitle = displayInfoOfProviderName(providerName).title; + const disabled = !providerEnabled; + const value = disabled ? false : !isHidden; + const tooltipName = disabled ? `Add ${providerTitle} to enable` : value === true ? "Show in Dropdown" : "Hide from Dropdown"; + const detailAboutModel = type === "autodetected" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Asterisk, { size: 14, className: "void-inline-block void-align-text-top void-brightness-115 void-stroke-[2] void-text-[#0e70c0]", "data-tooltip-id": "void-tooltip", "data-tooltip-place": "right", "data-tooltip-content": "Detected locally" }) : type === "custom" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Asterisk, { size: 14, className: "void-inline-block void-align-text-top void-brightness-115 void-stroke-[2] void-text-[#0e70c0]", "data-tooltip-id": "void-tooltip", "data-tooltip-place": "right", "data-tooltip-content": "Custom model" }) : void 0; + const hasOverrides = !!settingsState.overridesOfModel?.[providerName]?.[modelName2]; + return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)( + "div", + { + className: `void-flex void-items-center void-justify-between void-gap-4 hover:void-bg-black/10 dark:hover:void-bg-gray-300/10 void-py-1 void-px-3 void-rounded-sm void-overflow-hidden void-cursor-default void-truncate void-group `, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: `void-flex void-flex-grow void-items-center void-gap-4`, children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-w-full void-max-w-32", children: isNewProviderName ? providerTitle : "" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-w-fit void-max-w-[400px] void-truncate", children: modelName2 }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-2 void-w-fit", children: [ + disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-w-5 void-flex void-items-center void-justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + onClick: () => { + setOpenSettingsModel({ modelName: modelName2, providerName, type }); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "right", + "data-tooltip-content": "Advanced Settings", + className: `${hasOverrides ? "" : "void-opacity-0 group-hover:void-opacity-100"} void-transition-opacity`, + children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Plus, { size: 12, className: "void-text-void-fg-3 void-opacity-50" }) + } + ) }), + detailAboutModel, + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + value, + onChange: () => { + settingsStateService.toggleModelHidden(providerName, modelName2); + }, + disabled, + size: "sm", + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "right", + "data-tooltip-content": tooltipName + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: `void-w-5 void-flex void-items-center void-justify-center`, children: type === "default" || type === "autodetected" ? null : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + onClick: () => { + settingsStateService.deleteModel(providerName, modelName2); + }, + "data-tooltip-id": "void-tooltip", + "data-tooltip-place": "right", + "data-tooltip-content": "Delete", + className: `${hasOverrides ? "" : "void-opacity-0 group-hover:void-opacity-100"} void-transition-opacity`, + children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(X, { size: 12, className: "void-text-void-fg-3 void-opacity-50" }) + } + ) }) + ] }) + ] + }, + `${modelName2}${providerName}` + ); + }), + showCheckmark ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-mt-4", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(AnimatedCheckmarkButton, { text: "Added", className: "void-bg-[#0e70c0] void-text-white void-px-3 void-py-1 void-rounded-sm" }) }) : isAddModelOpen ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-mt-4", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("form", { className: "void-flex void-items-center void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidCustomDropdownBox, + { + options: providersToShow, + selectedOption: userChosenProviderName, + onChangeOption: (pn) => setUserChosenProviderName(pn), + getOptionDisplayName: (pn) => pn ? displayInfoOfProviderName(pn).title : "Provider Name", + getOptionDropdownName: (pn) => pn ? displayInfoOfProviderName(pn).title : "Provider Name", + getOptionsEqual: (a, b) => a === b, + className: "void-max-w-32 void-mx-2 void-w-full void-resize-none void-bg-void-bg-1 void-text-void-fg-1 placeholder:void-text-void-fg-3 void-border void-border-void-border-2 focus:void-border-void-border-1 void-py-1 void-px-2 void-rounded", + arrowTouchesText: false + } + ) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSimpleInputBox, + { + value: modelName, + compact: true, + onChangeValue: setModelName, + placeholder: "Model Name", + className: "void-max-w-32" + } + ) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + AddButton, + { + type: "button", + disabled: !modelName || !userChosenProviderName, + onClick: handleAddModel + } + ) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + type: "button", + onClick: () => { + setIsAddModelOpen(false); + setErrorString(""); + setModelName(""); + setUserChosenProviderName(null); + }, + className: "void-text-void-fg-4", + children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(X, { className: "void-size-4" }) + } + ) + ] }), + errorString && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-red-500 void-truncate void-whitespace-nowrap void-mt-1", children: errorString }) + ] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "div", + { + className: "void-text-void-fg-4 void-flex void-flex-nowrap void-text-nowrap void-items-center hover:void-brightness-110 void-cursor-pointer void-mt-4", + onClick: () => setIsAddModelOpen(true), + children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Plus, { size: 16 }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "Add a model" }) + ] }) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + SimpleModelSettingsDialog, + { + isOpen: openSettingsModel !== null, + onClose: () => setOpenSettingsModel(null), + modelInfo: openSettingsModel + } + ) + ] }); +}; +var ProviderSetting = ({ providerName, settingName, subTextMd }) => { + const { title: settingTitle, placeholder, isPasswordField } = displayInfoOfSettingName(providerName, settingName); + const accessor = useAccessor(); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + const settingsState = useSettingsState(); + const settingValue = settingsState.settingsOfProvider[providerName][settingName]; + if (typeof settingValue !== "string") { + console.log("Error: Provider setting had a non-string value."); + return; + } + const handleChangeValue = (0, import_react21.useCallback)((newVal) => { + cortexideSettingsService.setSettingOfProvider(providerName, settingName, newVal); + }, [cortexideSettingsService, providerName, settingName]); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-my-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSimpleInputBox, + { + value: settingValue, + onChangeValue: handleChangeValue, + placeholder: `${settingTitle} (${placeholder})`, + passwordBlur: isPasswordField, + compact: true + } + ), + !subTextMd ? null : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-py-1 void-px-3 void-opacity-50 void-text-sm", children: subTextMd }) + ] }) }); +}; +var SettingsForProvider = ({ providerName, showProviderTitle, showProviderSuggestions }) => { + const voidSettingsState = useSettingsState(); + const needsModel = isProviderNameDisabled(providerName, voidSettingsState) === "addModel"; + const settingNames = customSettingNamesOfProvider(providerName); + const { title: providerTitle } = displayInfoOfProviderName(providerName); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-flex void-items-center void-w-full void-gap-4", children: showProviderTitle && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { className: "void-text-xl void-truncate", children: providerTitle }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-px-0", children: [ + settingNames.map((settingName, i) => { + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + ProviderSetting, + { + providerName, + settingName, + subTextMd: i !== settingNames.length - 1 ? null : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatMarkdownRender, { string: subTextMdOfProviderName(providerName), chatMessageLocation: void 0 }) + }, + settingName + ); + }), + showProviderSuggestions && needsModel ? providerName === "ollama" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(WarningBox, { className: "void-pl-2 void-mb-4", text: `Please install an Ollama model. We'll auto-detect it.` }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(WarningBox, { className: "void-pl-2 void-mb-4", text: `Please add a model for ${providerTitle} (Models section).` }) : null + ] }) + ] }); +}; +var VoidProviderSettings = ({ providerNames: providerNames3 }) => { + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children: providerNames3.map( + (providerName) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SettingsForProvider, { providerName, showProviderTitle: true, showProviderSuggestions: true }, providerName) + ) }); +}; +var AutoDetectLocalModelsToggle = () => { + const settingName = "autoRefreshModels"; + const accessor = useAccessor(); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + const metricsService = accessor.get("IMetricsService"); + const voidSettingsState = useSettingsState(); + const enabled = voidSettingsState.globalSettings[settingName]; + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + ButtonLeftTextRightOption, + { + leftButton: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xxs", + value: enabled, + onChange: (newVal) => { + cortexideSettingsService.setGlobalSetting(settingName, newVal); + metricsService.capture("Click", { action: "Autorefresh Toggle", settingName, enabled: newVal }); + } + } + ), + text: `Automatically detect local providers and models (${refreshableProviderNames.map((providerName) => displayInfoOfProviderName(providerName).title).join(", ")}).` + } + ); +}; +var AIInstructionsBox = () => { + const accessor = useAccessor(); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + const voidSettingsState = useSettingsState(); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidInputBox2, + { + className: "void-min-h-[81px] void-p-3 void-rounded-sm", + initValue: voidSettingsState.globalSettings.aiInstructions, + placeholder: `Do not change my indentation or delete my comments. When writing TS or JS, do not add ;'s. Write new code using Rust if possible. `, + multiline: true, + onChangeText: (newText) => { + cortexideSettingsService.setGlobalSetting("aiInstructions", newText); + } + } + ); +}; +var FastApplyMethodDropdown = () => { + const accessor = useAccessor(); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + const options2 = (0, import_react21.useMemo)(() => [true, false], []); + const onChangeOption = (0, import_react21.useCallback)((newVal) => { + cortexideSettingsService.setGlobalSetting("enableFastApply", newVal); + }, [cortexideSettingsService]); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidCustomDropdownBox, + { + className: "void-text-xs void-text-void-fg-3 void-bg-void-bg-1 void-border void-border-void-border-1 void-rounded void-p-0.5 void-px-1", + options: options2, + selectedOption: cortexideSettingsService.state.globalSettings.enableFastApply, + onChangeOption, + getOptionDisplayName: (val) => val ? "Fast Apply" : "Slow Apply", + getOptionDropdownName: (val) => val ? "Fast Apply" : "Slow Apply", + getOptionDropdownDetail: (val) => val ? "Output Search/Replace blocks" : "Rewrite whole files", + getOptionsEqual: (a, b) => a === b + } + ); +}; +var OllamaSetupInstructions = ({ sayWeAutoDetect }) => { + const accessor = useAccessor(); + const terminalToolService = accessor.get("ITerminalToolService"); + const nativeHostService = accessor.get("INativeHostService"); + const notificationService2 = accessor.get("INotificationService"); + const refreshModelService = accessor.get("IRefreshModelService"); + const repoIndexerService = accessor.get("IRepoIndexerService"); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + const [status, setStatus] = (0, import_react21.useState)("idle"); + const [statusText, setStatusText] = (0, import_react21.useState)(""); + const [method, setMethod] = (0, import_react21.useState)("auto"); + const [currentTerminalId, setCurrentTerminalId] = (0, import_react21.useState)(null); + const [terminalOutput, setTerminalOutput] = (0, import_react21.useState)(""); + const [modelTag, setModelTag] = (0, import_react21.useState)("llava"); + const [isHealthy, setIsHealthy] = (0, import_react21.useState)(null); + (0, import_react21.useEffect)(() => { + (async () => { + try { + const osProps = await nativeHostService.getOSProperties(); + const t = (osProps.type + "").toLowerCase(); + if (t.includes("windows")) setMethod("winget"); + else if (t.includes("darwin") || t.includes("mac")) setMethod("brew"); + else + setMethod("curl"); + } catch { + } + })(); + }, [nativeHostService]); + const onInstall = (0, import_react21.useCallback)(async () => { + try { + const osProps = await nativeHostService.getOSProperties(); + const isWindows = (osProps.type + "").toLowerCase().includes("windows"); + setStatus("running"); + setStatusText("Starting Ollama installation and opening the terminal..."); + const persistentTerminalId = await terminalToolService.createPersistentTerminal({ cwd: null }); + setCurrentTerminalId(persistentTerminalId); + try { + const commandService = accessor.get("ICommandService"); + await commandService.executeCommand("workbench.action.terminal.focus"); + } catch { + } + await terminalToolService.focusPersistentTerminal(persistentTerminalId); + let installCmd = ""; + if (isWindows) { + const m = method === "choco" ? "choco install ollama -y" : method === "winget" || method === "auto" ? "winget install --id Ollama.Ollama -e --accept-source-agreements --accept-package-agreements" : "winget install --id Ollama.Ollama -e --accept-source-agreements --accept-package-agreements"; + installCmd = `powershell -ExecutionPolicy Bypass -Command "${m}; Start-Sleep -Seconds 2; Start-Process -WindowStyle Hidden ollama serve"`; + } else { + const osName = (osProps.type + "").toLowerCase(); + if (osName.includes("darwin") || osName.includes("mac")) { + installCmd = 'bash -lc "set -e; if [ -d /Applications/Ollama.app ]; then \\\n+ echo [CortexIDE] Found /Applications/Ollama.app; open -a Ollama; \\\n+ else \\\n+ if [ -x /opt/homebrew/bin/brew ] || [ -x /usr/local/bin/brew ]; then \\\n+ eval "$([ -x /opt/homebrew/bin/brew ] && /opt/homebrew/bin/brew shellenv || /usr/local/bin/brew shellenv)"; \\\n+ else \\\n+ echo [CortexIDE] Bootstrapping Homebrew...; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"; \\\n+ eval "$([ -x /opt/homebrew/bin/brew ] && /opt/homebrew/bin/brew shellenv || /usr/local/bin/brew shellenv)"; \\\n+ fi; \\\n+ echo [CortexIDE] Installing Ollama via Homebrew Cask...; brew install --cask ollama || true; open -a Ollama; \\\n+ fi; \\\n+ echo [CortexIDE] Health check...; sleep 2; curl -fsS http://127.0.0.1:11434/api/tags >/dev/null 2>&1 && echo [CortexIDE] Ollama running || echo [CortexIDE] Ollama not reachable yet; "'; + } else { + installCmd = 'bash -lc "set -e; echo [CortexIDE] Installing Ollama (Linux); curl -fsSL https://ollama.com/install.sh | sh; (ollama serve >/dev/null 2>&1 &) || true; sleep 2; echo [CortexIDE] Health check; curl -fsS http://127.0.0.1:11434/api/tags >/dev/null 2>&1 && echo [CortexIDE] Ollama running || echo [CortexIDE] Ollama not reachable yet;"'; + } + } + setStatusText("Running installer in terminal..."); + const { resPromise } = await terminalToolService.runCommand(installCmd, { type: "persistent", persistentTerminalId }); + resPromise.catch(() => { + }); + cortexideSettingsService.setSettingOfProvider("ollama", "endpoint", "http://127.0.0.1:11434"); + refreshModelService.startRefreshingModels("ollama", { enableProviderOnSuccess: true, doNotFire: false }); + setStatus("running"); + setStatusText("Installer launched. Detecting models..."); + notificationService2.info("Ollama install started in the integrated terminal. Models will appear when ready."); + } catch (e) { + notificationService2.error("Failed to start Ollama install. Please try again or install manually."); + setStatus("error"); + setStatusText("Failed to start install. See terminal or try manual install."); + } + }, [terminalToolService, nativeHostService, notificationService2, refreshModelService, cortexideSettingsService, method]); + (0, import_react21.useCallback)(async () => { + if (currentTerminalId) { + await terminalToolService.focusPersistentTerminal(currentTerminalId); + } else { + try { + const commandService = accessor.get("ICommandService"); + await commandService.executeCommand("workbench.action.terminal.focus"); + } catch { + } + } + }, [currentTerminalId, terminalToolService]); + (0, import_react21.useEffect)(() => { + let tid; + const poll = async () => { + if (!currentTerminalId) return; + try { + const output = await terminalToolService.readTerminal(currentTerminalId); + setTerminalOutput(output); + } catch { + } + }; + if (currentTerminalId) { + poll(); + tid = setInterval(poll, 1500); + } + return () => { + if (tid) clearInterval(tid); + }; + }, [currentTerminalId, terminalToolService]); + (0, import_react21.useEffect)(() => { + let tid; + const ping = async () => { + try { + const res = await fetch("http://127.0.0.1:11434/api/tags", { method: "GET" }); + setIsHealthy(res.ok); + if (res.ok && status === "running") { + setStatus("done"); + setStatusText("Ollama is running. Models will appear shortly."); + } + } catch { + setIsHealthy(false); + } + }; + if (status === "running" || status === "done") { + ping(); + tid = setInterval(ping, 3e3); + } + return () => { + if (tid) clearInterval(tid); + }; + }, [status]); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "prose-p:void-my-0 prose-ol:void-list-decimal prose-p:void-py-0 prose-ol:void-my-0 prose-ol:void-py-0 prose-span:void-my-0 prose-span:void-py-0 void-text-void-fg-3 void-text-sm void-list-decimal void-select-text", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatMarkdownRender, { string: `Ollama Setup (rev 2025-10-30-1)`, chatMessageLocation: void 0 }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)( + "select", + { + className: "void-text-xs void-bg-void-bg-1 void-text-void-fg-1 void-border void-border-void-border-1 void-rounded void-px-1 void-py-0.5", + value: method, + onChange: (e) => setMethod(e.target.value), + title: "Install method", + children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "auto", children: "Auto" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "brew", children: "Homebrew (macOS)" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "curl", children: "Curl Script (macOS/Linux)" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "winget", children: "Winget (Windows)" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "choco", children: "Chocolatey (Windows)" }) + ] + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + className: "void-px-2 void-py-1 void-bg-void-bg-2 void-text-void-fg-1 void-border void-border-void-border-1 void-rounded hover:void-brightness-110 disabled:void-opacity-60", + onClick: onInstall, + disabled: status === "running", + children: status === "running" ? "Installing\u2026" : "Install Ollama" + } + ), + status === "error" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + className: "void-px-2 void-py-1 void-bg-void-bg-1 void-text-void-fg-3 void-border void-border-void-border-2 void-rounded hover:void-brightness-110", + onClick: () => { + setStatus("idle"); + setStatusText(""); + setTerminalOutput(""); + setIsHealthy(null); + }, + children: "Retry" + } + ), + isHealthy !== null && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: `void-text-xs void-px-2 void-py-0.5 void-rounded void-border ${isHealthy ? "void-border-green-500 void-text-green-500" : "void-border-void-border-2 void-text-void-fg-3"}`, children: isHealthy ? "Healthy" : "Waiting" }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: " void-pl-6 void-mt-2 void-flex void-items-center void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xxs", + value: !!cortexideSettingsService.state.globalSettings.enableAutoTuneOnPull, + onChange: (v) => cortexideSettingsService.setGlobalSetting("enableAutoTuneOnPull", !!v) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs", children: "Auto-tune after pull" }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-2 void-ml-4", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xxs", + value: !!cortexideSettingsService.state.globalSettings.enableRepoIndexer, + onChange: (v) => cortexideSettingsService.setGlobalSetting("enableRepoIndexer", !!v) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs", children: "Enable repo indexer" }) + ] }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: " void-pl-6 void-mt-2 void-flex void-items-center void-gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xxs", + value: cortexideSettingsService.state.globalSettings.useHeadlessBrowsing !== false, + onChange: (v) => cortexideSettingsService.setGlobalSetting("useHeadlessBrowsing", v) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs", children: "Use headless browsing" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-4 void-text-xs", title: "Use headless BrowserWindow for better content extraction from complex pages. Disable to use direct HTTP fetch instead.", children: "(\u2139\uFE0F)" }) + ] }) }), + status !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: " void-pl-6 void-text-void-fg-3", children: statusText }), + !!terminalOutput && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: " void-pl-6 void-mt-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-2 void-mb-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + className: "void-px-2 void-py-0.5 void-bg-void-bg-1 void-text-void-fg-3 void-border void-border-void-border-2 void-rounded hover:void-brightness-110", + onClick: async () => { + try { + await navigator.clipboard.writeText(terminalOutput); + } catch { + } + }, + children: "Copy log" + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + className: "void-px-2 void-py-0.5 void-bg-void-bg-1 void-text-void-fg-3 void-border void-border-void-border-2 void-rounded hover:void-brightness-110", + onClick: () => setTerminalOutput(""), + children: "Clear" + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-border void-border-void-border-2 void-bg-void-bg-1 void-rounded void-p-2 void-max-h-48 void-overflow-auto void-text-xs void-whitespace-pre-wrap", children: terminalOutput }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: " void-pl-6 void-mt-2 void-flex void-items-center void-gap-2 void-whitespace-nowrap", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs", children: "Pull model:" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)( + "select", + { + className: "void-text-xs void-bg-void-bg-1 void-text-void-fg-1 void-border void-border-void-border-1 void-rounded void-px-1 void-py-0.5 void-shrink-0", + value: modelTag, + onChange: (e) => setModelTag(e.target.value), + children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("optgroup", { label: "Code Models", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "llama3.1", children: "llama3.1" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "llama3.2", children: "llama3.2" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "qwen2.5-coder", children: "qwen2.5-coder" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "deepseek-coder", children: "deepseek-coder" }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("optgroup", { label: "Vision Models (Image Analysis)", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "llava", children: "llava (Vision)" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "bakllava", children: "bakllava (Vision)" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "llava:13b", children: "llava:13b (Vision, Better Quality)" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "llava:7b", children: "llava:7b (Vision, Faster)" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "bakllava:7b", children: "bakllava:7b (Vision)" }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("optgroup", { label: "General Purpose", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "llama3", children: "llama3" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "mistral", children: "mistral" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "mixtral", children: "mixtral" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "qwen", children: "qwen" }) + ] }) + ] + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + className: "void-px-2 void-py-1 void-bg-void-bg-2 void-text-void-fg-1 void-border void-border-void-border-1 void-rounded hover:void-brightness-110 void-shrink-0 disabled:void-opacity-50 disabled:void-cursor-not-allowed", + disabled: !modelTag || status === "running", + onClick: async () => { + if (!modelTag) { + notificationService2.warn("Please select a model to pull."); + return; + } + try { + setStatus("running"); + setStatusText(`Pulling ${modelTag}...`); + let terminalId = currentTerminalId; + if (!terminalId || !terminalToolService.persistentTerminalExists(terminalId)) { + terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }); + setCurrentTerminalId(terminalId); + } + await terminalToolService.focusPersistentTerminal(terminalId); + const { resPromise } = await terminalToolService.runCommand(`ollama pull ${modelTag}`, { type: "persistent", persistentTerminalId: terminalId }); + resPromise.then(async ({ result, resolveReason }) => { + if (resolveReason.type === "done") { + if (resolveReason.exitCode === 0) { + const resultText = result || ""; + if (resultText.toLowerCase().includes("error") || resultText.toLowerCase().includes("failed")) { + setStatus("error"); + setStatusText(`Failed to pull ${modelTag}. Check terminal for details.`); + notificationService2.error(`Failed to pull model "${modelTag}". See terminal for details.`); + return; + } + setStatus("done"); + setStatusText(`Successfully pulled ${modelTag}`); + notificationService2.info(`Model "${modelTag}" pulled successfully.`); + setTimeout(() => { + refreshModelService.startRefreshingModels("ollama", { enableProviderOnSuccess: true, doNotFire: false }); + try { + if (cortexideSettingsService.state.globalSettings.enableAutoTuneOnPull) { + const mt = (modelTag || "").toLowerCase(); + const looksFIM = mt.includes("coder") || mt.includes("starcoder") || mt.includes("code"); + cortexideSettingsService.setOverridesOfModel("ollama", modelTag, { + supportsFIM: looksFIM, + contextWindow: looksFIM ? 128e3 : 64e3, + reservedOutputTokenSpace: 8192, + supportsSystemMessage: "system-role" + }); + if (looksFIM) { + cortexideSettingsService.setGlobalSetting("enableAutocomplete", true); + cortexideSettingsService.setModelSelectionOfFeature("Autocomplete", { providerName: "ollama", modelName: modelTag }); + cortexideSettingsService.setModelSelectionOfFeature("Apply", { providerName: "ollama", modelName: modelTag }); + } else { + cortexideSettingsService.setModelSelectionOfFeature("Chat", { providerName: "ollama", modelName: modelTag }); + } + } + } catch (e) { + console.error("Auto-tune error:", e); + } + try { + if (cortexideSettingsService.state.globalSettings.enableRepoIndexer) { + notificationService2.info("Warming project index..."); + repoIndexerService.warmIndex(void 0).then(() => { + notificationService2.info("Project index warmed."); + }).catch(() => { + }); + } + } catch { + } + }, 3e3); + } else { + const resultText = result || "Unknown error"; + setStatus("error"); + setStatusText(`Failed to pull ${modelTag} (exit code ${resolveReason.exitCode}). Check terminal for details.`); + notificationService2.error(`Failed to pull model "${modelTag}": ${resultText}. See terminal for details.`); + } + } else if (resolveReason.type === "timeout") { + setStatus("done"); + setStatusText(`Pulling ${modelTag}... (may take time for large models)`); + notificationService2.info(`Started pulling "${modelTag}". This may take a while for large models. Check terminal for progress.`); + setTimeout(() => { + refreshModelService.startRefreshingModels("ollama", { enableProviderOnSuccess: true, doNotFire: false }); + }, 5e3); + } + }).catch((error2) => { + setStatus("error"); + const errorMsg = error2?.message || String(error2) || "Unknown error"; + setStatusText(`Error pulling ${modelTag}: ${errorMsg}`); + notificationService2.error(`Failed to pull model "${modelTag}": ${errorMsg}`); + console.error("Pull error:", error2); + }); + } catch (error2) { + setStatus("error"); + const errorMsg = error2?.message || String(error2) || "Unknown error"; + setStatusText(`Failed to start pull: ${errorMsg}`); + notificationService2.error(`Failed to start pulling model "${modelTag}": ${errorMsg}`); + console.error("Pull setup error:", error2); + } + }, + children: "Pull" + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + className: "void-px-2 void-py-1 void-bg-red-600/80 void-text-white void-border void-border-red-500/80 void-rounded hover:void-brightness-110 void-shrink-0 disabled:void-opacity-50 disabled:void-cursor-not-allowed", + disabled: !modelTag || status === "running", + onClick: async () => { + if (!modelTag) { + notificationService2.warn("Please select a model to delete."); + return; + } + const ok = window.confirm(`Delete model "${modelTag}" from Ollama?`); + if (!ok) return; + try { + setStatus("running"); + setStatusText(`Deleting ${modelTag}...`); + let terminalId = currentTerminalId; + if (!terminalId || !terminalToolService.persistentTerminalExists(terminalId)) { + terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }); + setCurrentTerminalId(terminalId); + } + await terminalToolService.focusPersistentTerminal(terminalId); + const { resPromise } = await terminalToolService.runCommand(`ollama rm ${modelTag}`, { type: "persistent", persistentTerminalId: terminalId }); + resPromise.then(async ({ result, resolveReason }) => { + if (resolveReason.type === "done") { + if (resolveReason.exitCode === 0) { + setStatus("done"); + setStatusText(`Successfully deleted ${modelTag}`); + notificationService2.info(`Model "${modelTag}" deleted successfully.`); + setTimeout(() => { + refreshModelService.startRefreshingModels("ollama", { enableProviderOnSuccess: true, doNotFire: false }); + }, 2e3); + } else { + const resultText = result || "Unknown error"; + setStatus("error"); + setStatusText(`Failed to delete ${modelTag} (exit code ${resolveReason.exitCode}). Check terminal for details.`); + notificationService2.error(`Failed to delete model "${modelTag}": ${resultText}. See terminal for details.`); + } + } else if (resolveReason.type === "timeout") { + setStatus("error"); + setStatusText(`Delete command timed out for ${modelTag}. The command may still be running.`); + notificationService2.warn(`Delete command for "${modelTag}" timed out. Check terminal to see if it completed.`); + setTimeout(() => { + refreshModelService.startRefreshingModels("ollama", { enableProviderOnSuccess: true, doNotFire: false }); + }, 2e3); + } + }).catch((error2) => { + setStatus("error"); + const errorMsg = error2?.message || String(error2) || "Unknown error"; + setStatusText(`Error deleting ${modelTag}: ${errorMsg}`); + notificationService2.error(`Failed to delete model "${modelTag}": ${errorMsg}`); + console.error("Delete error:", error2); + }); + } catch (error2) { + setStatus("error"); + const errorMsg = error2?.message || String(error2) || "Unknown error"; + setStatusText(`Failed to start delete: ${errorMsg}`); + notificationService2.error(`Failed to start deleting model "${modelTag}": ${errorMsg}`); + console.error("Delete setup error:", error2); + } + }, + children: "Delete" + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: " void-pl-6", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatMarkdownRender, { string: `1. If the install does not start, download Ollama manually from [ollama.com/download](https://ollama.com/download).`, chatMessageLocation: void 0 }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: " void-pl-6", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatMarkdownRender, { string: `2. Optionally, run \`ollama pull llama3.1\` to install a starter model.`, chatMessageLocation: void 0 }) }), + sayWeAutoDetect && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: " void-pl-6", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatMarkdownRender, { string: `CortexIDE automatically detects locally running models and enables them.`, chatMessageLocation: void 0 }) }) + ] }); +}; +var RedoOnboardingButton = ({ className }) => { + const accessor = useAccessor(); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "div", + { + className: `void-text-void-fg-4 void-flex void-flex-nowrap void-text-nowrap void-items-center hover:void-brightness-110 void-cursor-pointer ${className}`, + onClick: () => { + cortexideSettingsService.setGlobalSetting("isOnboardingComplete", false); + }, + children: "See onboarding screen?" + } + ); +}; +var ToolApprovalTypeSwitch = ({ approvalType, size: size3, desc }) => { + const accessor = useAccessor(); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + const voidSettingsState = useSettingsState(); + const metricsService = accessor.get("IMetricsService"); + const onToggleAutoApprove = (0, import_react21.useCallback)((approvalType2, newValue) => { + cortexideSettingsService.setGlobalSetting("autoApprove", { + ...cortexideSettingsService.state.globalSettings.autoApprove, + [approvalType2]: newValue + }); + metricsService.capture("Tool Auto-Accept Toggle", { enabled: newValue }); + }, [cortexideSettingsService, metricsService]); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: size3, + value: voidSettingsState.globalSettings.autoApprove[approvalType] ?? false, + onChange: (newVal) => onToggleAutoApprove(approvalType, newVal) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs", children: desc }) + ] }); +}; +var OneClickSwitchButton = ({ fromEditor = "VS Code", className = "" }) => { + const accessor = useAccessor(); + const extensionTransferService = accessor.get("IExtensionTransferService"); + const [transferState, setTransferState] = (0, import_react21.useState)({ type: "done" }); + const onClick = async () => { + if (transferState.type !== "done") return; + setTransferState({ type: "loading" }); + const errAcc = await extensionTransferService.transferExtensions(os, fromEditor); + const hadError = !!errAcc; + if (hadError) { + setTransferState({ type: "done", error: errAcc }); + } else { + setTransferState({ type: "justfinished" }); + setTimeout(() => { + setTransferState({ type: "done" }); + }, 3e3); + } + }; + return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className: `void-max-w-48 void-p-4 ${className}`, disabled: transferState.type !== "done", onClick, children: transferState.type === "done" ? `Transfer from ${fromEditor}` : transferState.type === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "void-text-nowrap void-flex void-flex-nowrap", children: [ + "Transferring", + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconLoading, {}) + ] }) : transferState.type === "justfinished" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(AnimatedCheckmarkButton, { text: "Settings Transferred", className: "void-bg-none" }) : null }), + transferState.type === "done" && transferState.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(WarningBox, { text: transferState.error }) : null + ] }); +}; +var MCPServerComponent = ({ name, server }) => { + const accessor = useAccessor(); + const mcpService = accessor.get("IMCPService"); + const voidSettings = useSettingsState(); + const isOn = voidSettings.mcpUserStateOfName[name]?.isOn; + const removeUniquePrefix = (name2) => name2.split("_").slice(1).join("_"); + return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-border void-border-void-border-2 void-bg-void-bg-1 void-py-3 void-px-4 void-rounded-sm void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-justify-between", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: `void-w-2 void-h-2 void-rounded-full ${server.status === "success" ? "void-bg-green-500" : server.status === "error" ? "void-bg-red-500" : server.status === "loading" ? "void-bg-yellow-500" : server.status === "offline" ? "void-bg-void-fg-3" : ""} ` }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-sm void-font-medium void-text-void-fg-1", children: name }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + value: isOn ?? false, + size: "xs", + disabled: server.status === "error", + onChange: () => mcpService.toggleServerIsOn(name, !isOn) + } + ) + ] }), + isOn && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-flex void-flex-wrap void-gap-2 void-max-h-32 void-overflow-y-auto", children: (server.tools ?? []).length > 0 ? (server.tools ?? []).map( + (tool) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "span", + { + className: "void-px-2 void-py-0.5 void-bg-void-bg-2 void-text-void-fg-3 void-rounded-sm void-text-xs", + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": tool.description || "", + "data-tooltip-class-name": "void-max-w-[300px]", + children: removeUniquePrefix(tool.name) + }, + tool.name + ) + ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-xs void-text-void-fg-3", children: "No tools available" }) }) }), + isOn && server.command && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-mt-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-xs void-text-void-fg-3 void-mb-1", children: "Command:" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-px-2 void-py-1 void-bg-void-bg-2 void-text-xs void-font-mono void-overflow-x-auto void-whitespace-nowrap void-text-void-fg-2 void-rounded-sm", children: server.command }) + ] }), + server.error && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(WarningBox, { text: server.error }) }) + ] }); +}; +var MCPServersList = () => { + const mcpServiceState = useMCPServiceState(); + let content; + if (mcpServiceState.error) { + content = /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-void-fg-3 void-text-sm void-mt-2", children: mcpServiceState.error }); + } else { + const entries = Object.entries(mcpServiceState.mcpServerOfName); + if (entries.length === 0) { + content = /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-void-fg-3 void-text-sm void-mt-2", children: "No servers found" }); + } else { + content = entries.map( + ([name, server]) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MCPServerComponent, { name, server }, name) + ); + } + } + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-my-2", children: content }); +}; +var Settings = () => { + const isDark = useIsDark(); + const [selectedSection, setSelectedSection] = (0, import_react21.useState)("models"); + const navItems = [ + { tab: "models", label: "Models" }, + { tab: "localProviders", label: "Local Providers" }, + { tab: "providers", label: "Main Providers" }, + { tab: "featureOptions", label: "Feature Options" }, + { tab: "general", label: "General" }, + { tab: "mcp", label: "MCP" }, + { tab: "all", label: "All Settings" } + ]; + const shouldShowTab = (tab) => selectedSection === "all" || selectedSection === tab; + const accessor = useAccessor(); + const commandService = accessor.get("ICommandService"); + const environmentService = accessor.get("IEnvironmentService"); + const nativeHostService = accessor.get("INativeHostService"); + const settingsState = useSettingsState(); + const cortexideSettingsService = accessor.get("ICortexideSettingsService"); + const chatThreadsService = accessor.get("IChatThreadService"); + const notificationService2 = accessor.get("INotificationService"); + const mcpService = accessor.get("IMCPService"); + const storageService = accessor.get("IStorageService"); + const metricsService = accessor.get("IMetricsService"); + const isOptedOut = useIsOptedOut(); + const onDownload = (t) => { + let dataStr; + let downloadName; + if (t === "Chats") { + dataStr = JSON.stringify(chatThreadsService.state, null, 2); + downloadName = "void-chats.json"; + } else if (t === "Settings") { + dataStr = JSON.stringify(cortexideSettingsService.state, null, 2); + downloadName = "void-settings.json"; + } else { + dataStr = ""; + downloadName = ""; + } + const blob = new Blob([dataStr], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = downloadName; + a.click(); + URL.revokeObjectURL(url); + }; + const fileInputSettingsRef = (0, import_react21.useRef)(null); + const fileInputChatsRef = (0, import_react21.useRef)(null); + const [s, ss] = (0, import_react21.useState)(0); + const handleUpload = (t) => (e) => { + const files = e.target.files; + if (!files) return; + const file = files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + try { + const json = JSON.parse(reader.result); + if (t === "Chats") { + chatThreadsService.dangerousSetState(json); + } else if (t === "Settings") { + cortexideSettingsService.dangerousSetState(json); + } + notificationService2.info(`${t} imported successfully!`); + } catch (err) { + notificationService2.notify({ message: `Failed to import ${t}`, source: err + "", severity: Severity.Error }); + } + }; + reader.readAsText(file); + e.target.value = ""; + ss((s2) => s2 + 1); + }; + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: `void-scope ${isDark ? "void-dark" : ""}`, style: { height: "100%", width: "100%", overflow: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-flex-col md:void-flex-row void-w-full void-gap-6 void-max-w-[900px] void-mx-auto void-mb-32", style: { minHeight: "80vh" }, children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("aside", { className: "md:void-w-1/4 void-w-full void-p-6 void-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-flex void-flex-col void-gap-2 void-mt-12", children: navItems.map( + ({ tab, label }) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "button", + { + onClick: () => { + if (tab === "all") { + setSelectedSection("all"); + window.scrollTo({ top: 0, behavior: "smooth" }); + } else { + setSelectedSection(tab); + } + }, + className: ` void-py-2 void-px-4 void-rounded-md void-text-left void-transition-all void-duration-200 ${selectedSection === tab ? "void-bg-[#0e70c0]/80 void-text-white void-font-medium void-shadow-sm" : "void-bg-void-bg-2 hover:void-bg-void-bg-2/80 void-text-void-fg-1"} `, + children: label + }, + tab + ) + ) }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("main", { className: "void-flex-1 void-p-6 void-select-none", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-max-w-3xl", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h1", { className: "void-text-2xl void-w-full", children: `CortexIDE Settings` }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-w-full void-h-[1px] void-my-2" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RedoOnboardingButton, {}) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-w-full void-h-[1px] void-my-4" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-flex-col void-gap-12", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: shouldShowTab("models") ? `` : "void-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(ErrorBoundary_default, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: `void-text-3xl void-mb-2`, children: "Models" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ModelDump, {}), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-w-full void-h-[1px] void-my-4" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(AutoDetectLocalModelsToggle, {}), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RefreshableModels, {}) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: shouldShowTab("localProviders") ? `` : "void-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(ErrorBoundary_default, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: `void-text-3xl void-mb-2`, children: "Local Providers" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { className: `void-text-void-fg-3 void-mb-2`, children: `CortexIDE can access any model that you host locally. We automatically detect your local models by default.` }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-opacity-80 void-mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OllamaSetupInstructions, { sayWeAutoDetect: true }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidProviderSettings, { providerNames: localProviderNames }) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: shouldShowTab("providers") ? `` : "void-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(ErrorBoundary_default, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: `void-text-3xl void-mb-2`, children: "Main Providers" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { className: `void-text-void-fg-3 void-mb-2`, children: `CortexIDE can access models from Anthropic, OpenAI, OpenRouter, and more.` }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidProviderSettings, { providerNames: nonlocalProviderNames }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-w-full void-h-[1px] void-my-4" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RefreshableRemoteCatalogs, {}) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: shouldShowTab("featureOptions") ? `` : "void-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(ErrorBoundary_default, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: `void-text-3xl void-mb-2`, children: "Feature Options" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-flex-col void-gap-y-8 void-my-4", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: `void-text-base`, children: displayInfoOfFeatureName("Autocomplete") }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-text-sm void-text-void-fg-3 void-mt-1", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { children: [ + "Experimental.", + " " + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "span", + { + className: "hover:void-brightness-110", + "data-tooltip-id": "void-tooltip", + "data-tooltip-content": "We recommend using the largest qwen2.5-coder model you can with Ollama (try qwen2.5-coder:3b).", + "data-tooltip-class-name": "void-max-w-[20px]", + children: "Only works with FIM models.*" + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2 void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xs", + value: settingsState.globalSettings.enableAutocomplete, + onChange: (newVal) => cortexideSettingsService.setGlobalSetting("enableAutocomplete", newVal) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: settingsState.globalSettings.enableAutocomplete ? "Enabled" : "Disabled" }) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: `void-my-2 ${!settingsState.globalSettings.enableAutocomplete ? "void-hidden" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ModelDropdown, { featureName: "Autocomplete", className: "void-text-xs void-text-void-fg-3 void-bg-void-bg-1 void-border void-border-void-border-1 void-rounded void-p-0.5 void-px-1" }) }) }) + ] }) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-w-full", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: `void-text-base`, children: displayInfoOfFeatureName("Apply") }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-sm void-text-void-fg-3 void-mt-1", children: "Settings that control the behavior of the Apply button." }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2 void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xs", + value: settingsState.globalSettings.syncApplyToChat, + onChange: (newVal) => cortexideSettingsService.setGlobalSetting("syncApplyToChat", newVal) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: settingsState.globalSettings.syncApplyToChat ? "Same as Chat model" : "Different model" }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: `void-my-2 ${settingsState.globalSettings.syncApplyToChat ? "void-hidden" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ModelDropdown, { featureName: "Apply", className: "void-text-xs void-text-void-fg-3 void-bg-void-bg-1 void-border void-border-void-border-1 void-rounded void-p-0.5 void-px-1" }) }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-my-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-flex void-items-center void-gap-x-2 void-my-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(FastApplyMethodDropdown, {}) }) }) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: `void-text-base`, children: "Tools" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-sm void-text-void-fg-3 void-mt-1", children: `Tools are functions that LLMs can call. Some tools require user approval.` }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: [...toolApprovalTypes].map((approvalType) => { + return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-flex void-items-center void-gap-x-2 void-my-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ToolApprovalTypeSwitch, { size: "xs", approvalType, desc: `Auto-approve ${approvalType}` }) }, approvalType); + }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2 void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xs", + value: settingsState.globalSettings.includeToolLintErrors, + onChange: (newVal) => cortexideSettingsService.setGlobalSetting("includeToolLintErrors", newVal) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: settingsState.globalSettings.includeToolLintErrors ? "Fix lint errors" : `Fix lint errors` }) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2 void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xs", + value: settingsState.globalSettings.autoAcceptLLMChanges, + onChange: (newVal) => cortexideSettingsService.setGlobalSetting("autoAcceptLLMChanges", newVal) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: "Auto-accept LLM changes" }) + ] }) }) + ] }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: `void-text-base`, children: "YOLO Mode" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-sm void-text-void-fg-3 void-mt-1", children: "Automatically apply low-risk edits without approval. High-risk edits always require approval." }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2 void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xs", + value: settingsState.globalSettings.enableYOLOMode ?? false, + onChange: (newVal) => cortexideSettingsService.setGlobalSetting("enableYOLOMode", newVal) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: settingsState.globalSettings.enableYOLOMode ? "Enabled" : "Disabled" }) + ] }) }), + settingsState.globalSettings.enableYOLOMode && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-my-4 void-space-y-3", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "void-text-sm void-text-void-fg-2 void-mb-1 void-block", children: [ + "Risk Threshold: ", + (settingsState.globalSettings.yoloRiskThreshold ?? 0.2).toFixed(2) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-xs void-text-void-fg-3 void-mb-2", children: "Edits with risk below this threshold will auto-apply (0.0 = safe, 1.0 = dangerous)" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "input", + { + type: "range", + min: "0", + max: "1", + step: "0.05", + value: settingsState.globalSettings.yoloRiskThreshold ?? 0.2, + onChange: (e) => cortexideSettingsService.setGlobalSetting("yoloRiskThreshold", parseFloat(e.target.value)), + className: "void-w-full" + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "void-text-sm void-text-void-fg-2 void-mb-1 void-block", children: [ + "Confidence Threshold: ", + (settingsState.globalSettings.yoloConfidenceThreshold ?? 0.7).toFixed(2) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-xs void-text-void-fg-3 void-mb-2", children: "Edits with confidence above this threshold will auto-apply (0.0 = uncertain, 1.0 = confident)" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + "input", + { + type: "range", + min: "0", + max: "1", + step: "0.05", + value: settingsState.globalSettings.yoloConfidenceThreshold ?? 0.7, + onChange: (e) => cortexideSettingsService.setGlobalSetting("yoloConfidenceThreshold", parseFloat(e.target.value)), + className: "void-w-full" + } + ) + ] }) + ] }) + ] }) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-w-full", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: `void-text-base`, children: "Editor" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-sm void-text-void-fg-3 void-mt-1", children: `Settings that control the visibility of CortexIDE suggestions in the code editor.` }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-my-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2 void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xs", + value: settingsState.globalSettings.showInlineSuggestions, + onChange: (newVal) => cortexideSettingsService.setGlobalSetting("showInlineSuggestions", newVal) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: settingsState.globalSettings.showInlineSuggestions ? "Show suggestions on select" : "Show suggestions on select" }) + ] }) }) }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-w-full", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: `void-text-base`, children: displayInfoOfFeatureName("SCM") }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-sm void-text-void-fg-3 void-mt-1", children: "Settings that control the behavior of the commit message generator." }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2 void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xs", + value: settingsState.globalSettings.syncSCMToChat, + onChange: (newVal) => cortexideSettingsService.setGlobalSetting("syncSCMToChat", newVal) + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: settingsState.globalSettings.syncSCMToChat ? "Same as Chat model" : "Different model" }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: `void-my-2 ${settingsState.globalSettings.syncSCMToChat ? "void-hidden" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ModelDropdown, { featureName: "SCM", className: "void-text-xs void-text-void-fg-3 void-bg-void-bg-1 void-border void-border-void-border-1 void-rounded void-p-0.5 void-px-1" }) }) + ] }) + ] }) }) + ] }) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: `${shouldShowTab("general") ? `` : "void-hidden"} void-flex void-flex-col void-gap-12`, children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(ErrorBoundary_default, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: "void-text-3xl void-mb-2", children: "One-Click Switch" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: "void-text-void-fg-3 void-mb-4", children: `Transfer your editor settings into CortexIDE.` }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-flex-col void-gap-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OneClickSwitchButton, { className: "void-w-48", fromEditor: "VS Code" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OneClickSwitchButton, { className: "void-w-48", fromEditor: "Cursor" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(OneClickSwitchButton, { className: "void-w-48", fromEditor: "Windsurf" }) + ] }) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: "void-text-3xl void-mb-2", children: "Import/Export" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: "void-text-void-fg-3 void-mb-4", children: `Transfer CortexIDE's settings and chats in and out of CortexIDE.` }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-flex-col void-gap-8", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-flex-col void-gap-2 void-max-w-48 void-w-full", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("input", { ref: fileInputSettingsRef, type: "file", accept: ".json", className: "void-hidden", onChange: handleUpload("Settings") }, 2 * s), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className: "void-px-4 void-py-1 void-w-full", onClick: () => { + fileInputSettingsRef.current?.click(); + }, children: "Import Settings" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className: "void-px-4 void-py-1 void-w-full", onClick: () => onDownload("Settings"), children: "Export Settings" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ConfirmButton, { className: "void-px-4 void-py-1 void-w-full", onConfirm: () => { + cortexideSettingsService.resetState(); + }, children: "Reset Settings" }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-flex-col void-gap-2 void-max-w-48 void-w-full", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("input", { ref: fileInputChatsRef, type: "file", accept: ".json", className: "void-hidden", onChange: handleUpload("Chats") }, 2 * s + 1), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className: "void-px-4 void-py-1 void-w-full", onClick: () => { + fileInputChatsRef.current?.click(); + }, children: "Import Chats" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className: "void-px-4 void-py-1 void-w-full", onClick: () => onDownload("Chats"), children: "Export Chats" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ConfirmButton, { className: "void-px-4 void-py-1 void-w-full", onConfirm: () => { + chatThreadsService.resetState(); + }, children: "Reset Chats" }) + ] }) + ] }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: `void-text-3xl void-mb-2`, children: "Built-in Settings" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: `void-text-void-fg-3 void-mb-4`, children: `IDE settings, keyboard settings, and theme customization.` }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-flex-col void-gap-2 void-justify-center void-max-w-48 void-w-full", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className: "void-px-4 void-py-1", onClick: () => { + commandService.executeCommand("workbench.action.openSettings"); + }, children: "General Settings" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className: "void-px-4 void-py-1", onClick: () => { + commandService.executeCommand("workbench.action.openGlobalKeybindings"); + }, children: "Keyboard Settings" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className: "void-px-4 void-py-1", onClick: () => { + commandService.executeCommand("workbench.action.selectTheme"); + }, children: "Theme Settings" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className: "void-px-4 void-py-1", onClick: () => { + nativeHostService.showItemInFolder(environmentService.logsHome.fsPath); + }, children: "Open Logs" }) + ] }) }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-max-w-[600px]", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: `void-text-3xl void-mb-2`, children: "Metrics" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: `void-text-void-fg-3 void-mb-4`, children: "Very basic anonymous usage tracking helps us keep CortexIDE running smoothly. You may opt out below. Regardless of this setting, CortexIDE never sees your code, messages, or API keys." }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-my-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2 void-my-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xs", + value: isOptedOut, + onChange: (newVal) => { + storageService.store(OPT_OUT_KEY, newVal, StorageScope.APPLICATION, StorageTarget.MACHINE); + metricsService.capture(`Set metrics opt-out to ${newVal}`, {}); + } + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: "Opt-out (requires restart)" }) + ] }) }) }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-max-w-[600px]", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: `void-text-3xl void-mb-2`, children: "AI Instructions" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: `void-text-void-fg-3 void-mb-4`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatMarkdownRender, { inPTag: true, string: ` +System instructions to include with all AI requests. +Alternatively, place a \`.voidrules\` file in the root of your workspace. + `, chatMessageLocation: void 0 }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(AIInstructionsBox, {}) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-my-4", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "void-flex void-items-center void-gap-x-2", children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)( + VoidSwitch, + { + size: "xs", + value: !!settingsState.globalSettings.disableSystemMessage, + onChange: (newValue) => { + cortexideSettingsService.setGlobalSetting("disableSystemMessage", newValue); + } + } + ), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "void-text-void-fg-3 void-text-xs void-pointer-events-none", children: "Disable system message" }) + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-text-void-fg-3 void-text-xs void-mt-1", children: `When disabled, CortexIDE will not include anything in the system message except for content you specified above.` }) + ] }) + ] }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: shouldShowTab("mcp") ? `` : "void-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(ErrorBoundary_default, { children: [ + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { className: "void-text-3xl void-mb-2", children: "MCP" }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h4", { className: `void-text-void-fg-3 void-mb-4`, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatMarkdownRender, { inPTag: true, string: ` +Use Model Context Protocol to provide Agent mode with more tools. + `, chatMessageLocation: void 0 }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "void-my-2", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VoidButtonBgDarken, { className: "void-px-4 void-py-1 void-w-full void-max-w-48", onClick: async () => { + await mcpService.revealMCPConfigFile(); + }, children: "Add MCP Server" }) }), + /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary_default, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MCPServersList, {}) }) + ] }) }) + ] }) + ] }) }) + ] }) }); +}; + +export { ErrorBoundary_default, ModelDump, OllamaSetupInstructions, OneClickSwitchButton, Settings, SettingsForProvider, SidebarChat, VoidChatArea, VoidInputBox2, useRefState }; diff --git a/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-JSBRDJBE.js b/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-JSBRDJBE.js new file mode 100644 index 00000000000..8702359e0f2 --- /dev/null +++ b/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-JSBRDJBE.js @@ -0,0 +1,27 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +export { __commonJS, __toESM }; diff --git a/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-PT4A2IRQ.js b/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-PT4A2IRQ.js new file mode 100644 index 00000000000..53dbfb52026 --- /dev/null +++ b/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-PT4A2IRQ.js @@ -0,0 +1,415 @@ +import { require_react } from './chunk-RJP66NWB.js'; +import { __toESM } from './chunk-JSBRDJBE.js'; + +// ../../../../../../../node_modules/lucide-react/dist/esm/createLucideIcon.js +var import_react2 = __toESM(require_react()); + +// ../../../../../../../node_modules/lucide-react/dist/esm/shared/src/utils.js +var toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); +var toCamelCase = (string) => string.replace( + /^([A-Z])|[\s-_]+(\w)/g, + (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase() +); +var toPascalCase = (string) => { + const camelCase = toCamelCase(string); + return camelCase.charAt(0).toUpperCase() + camelCase.slice(1); +}; +var mergeClasses = (...classes) => classes.filter((className, index, array) => { + return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index; +}).join(" ").trim(); +var hasA11yProp = (props) => { + for (const prop in props) { + if (prop.startsWith("aria-") || prop === "role" || prop === "title") { + return true; + } + } +}; + +// ../../../../../../../node_modules/lucide-react/dist/esm/Icon.js +var import_react = __toESM(require_react()); + +// ../../../../../../../node_modules/lucide-react/dist/esm/defaultAttributes.js +var defaultAttributes = { + xmlns: "http://www.w3.org/2000/svg", + width: 24, + height: 24, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: 2, + strokeLinecap: "round", + strokeLinejoin: "round" +}; + +// ../../../../../../../node_modules/lucide-react/dist/esm/Icon.js +var Icon = (0, import_react.forwardRef)( + ({ + color = "currentColor", + size = 24, + strokeWidth = 2, + absoluteStrokeWidth, + className = "", + children, + iconNode, + ...rest + }, ref) => { + return (0, import_react.createElement)( + "svg", + { + ref, + ...defaultAttributes, + width: size, + height: size, + stroke: color, + strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth, + className: mergeClasses("lucide", className), + ...!children && !hasA11yProp(rest) && { "aria-hidden": "true" }, + ...rest + }, + [ + ...iconNode.map(([tag, attrs]) => (0, import_react.createElement)(tag, attrs)), + ...Array.isArray(children) ? children : [children] + ] + ); + } +); + +// ../../../../../../../node_modules/lucide-react/dist/esm/createLucideIcon.js +var createLucideIcon = (iconName, iconNode) => { + const Component = (0, import_react2.forwardRef)( + ({ className, ...props }, ref) => (0, import_react2.createElement)(Icon, { + ref, + iconNode, + className: mergeClasses( + `lucide-${toKebabCase(toPascalCase(iconName))}`, + `lucide-${iconName}`, + className + ), + ...props + }) + ); + Component.displayName = toPascalCase(iconName); + return Component; +}; + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/asterisk.js +var __iconNode = [ + ["path", { d: "M12 6v12", key: "1vza4d" }], + ["path", { d: "M17.196 9 6.804 15", key: "1ah31z" }], + ["path", { d: "m6.804 9 10.392 6", key: "1b6pxd" }] +]; +var Asterisk = createLucideIcon("asterisk", __iconNode); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/check.js +var __iconNode2 = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]]; +var Check = createLucideIcon("check", __iconNode2); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/chevron-right.js +var __iconNode3 = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]]; +var ChevronRight = createLucideIcon("chevron-right", __iconNode3); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/ellipsis-vertical.js +var __iconNode4 = [ + ["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }], + ["circle", { cx: "12", cy: "5", r: "1", key: "gxeob9" }], + ["circle", { cx: "12", cy: "19", r: "1", key: "lyex9k" }] +]; +var EllipsisVertical = createLucideIcon("ellipsis-vertical", __iconNode4); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/loader-circle.js +var __iconNode5 = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]]; +var LoaderCircle = createLucideIcon("loader-circle", __iconNode5); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/lock.js +var __iconNode6 = [ + ["rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2", key: "1w4ew1" }], + ["path", { d: "M7 11V7a5 5 0 0 1 10 0v4", key: "fwvmzm" }] +]; +var Lock = createLucideIcon("lock", __iconNode6); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/move-down.js +var __iconNode7 = [ + ["path", { d: "M8 18L12 22L16 18", key: "cskvfv" }], + ["path", { d: "M12 2V22", key: "r89rzk" }] +]; +var MoveDown = createLucideIcon("move-down", __iconNode7); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/move-left.js +var __iconNode8 = [ + ["path", { d: "M6 8L2 12L6 16", key: "kyvwex" }], + ["path", { d: "M2 12H22", key: "1m8cig" }] +]; +var MoveLeft = createLucideIcon("move-left", __iconNode8); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/move-right.js +var __iconNode9 = [ + ["path", { d: "M18 8L22 12L18 16", key: "1r0oui" }], + ["path", { d: "M2 12H22", key: "1m8cig" }] +]; +var MoveRight = createLucideIcon("move-right", __iconNode9); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/move-up.js +var __iconNode10 = [ + ["path", { d: "M8 6L12 2L16 6", key: "1yvkyx" }], + ["path", { d: "M12 2V22", key: "r89rzk" }] +]; +var MoveUp = createLucideIcon("move-up", __iconNode10); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/plus.js +var __iconNode11 = [ + ["path", { d: "M5 12h14", key: "1ays0h" }], + ["path", { d: "M12 5v14", key: "s699le" }] +]; +var Plus = createLucideIcon("plus", __iconNode11); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/refresh-cw.js +var __iconNode12 = [ + ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }], + ["path", { d: "M21 3v5h-5", key: "1q7to0" }], + ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }], + ["path", { d: "M8 16H3v5", key: "1cv678" }] +]; +var RefreshCw = createLucideIcon("refresh-cw", __iconNode12); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/x.js +var __iconNode13 = [ + ["path", { d: "M18 6 6 18", key: "1bl5f8" }], + ["path", { d: "m6 6 12 12", key: "d8bk6v" }] +]; +var X = createLucideIcon("x", __iconNode13); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/ban.js +var __iconNode14 = [ + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], + ["path", { d: "m4.9 4.9 14.2 14.2", key: "1m5liu" }] +]; +var Ban = createLucideIcon("ban", __iconNode14); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/chevron-down.js +var __iconNode15 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]]; +var ChevronDown = createLucideIcon("chevron-down", __iconNode15); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/chevron-left.js +var __iconNode16 = [["path", { d: "m15 18-6-6 6-6", key: "1wnfg3" }]]; +var ChevronLeft = createLucideIcon("chevron-left", __iconNode16); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/chevron-up.js +var __iconNode17 = [["path", { d: "m18 15-6-6-6 6", key: "153udz" }]]; +var ChevronUp = createLucideIcon("chevron-up", __iconNode17); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/circle-alert.js +var __iconNode18 = [ + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], + ["line", { x1: "12", x2: "12", y1: "8", y2: "12", key: "1pkeuh" }], + ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16", key: "4dfq90" }] +]; +var CircleAlert = createLucideIcon("circle-alert", __iconNode18); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/circle-ellipsis.js +var __iconNode19 = [ + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], + ["path", { d: "M17 12h.01", key: "1m0b6t" }], + ["path", { d: "M12 12h.01", key: "1mp3jc" }], + ["path", { d: "M7 12h.01", key: "eqddd0" }] +]; +var CircleEllipsis = createLucideIcon("circle-ellipsis", __iconNode19); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/circle-plus.js +var __iconNode20 = [ + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], + ["path", { d: "M8 12h8", key: "1wcyev" }], + ["path", { d: "M12 8v8", key: "napkw2" }] +]; +var CirclePlus = createLucideIcon("circle-plus", __iconNode20); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/copy.js +var __iconNode21 = [ + ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }], + ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }] +]; +var Copy = createLucideIcon("copy", __iconNode21); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/dot.js +var __iconNode22 = [["circle", { cx: "12.1", cy: "12.1", r: "1", key: "18d7e5" }]]; +var Dot = createLucideIcon("dot", __iconNode22); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/file-symlink.js +var __iconNode23 = [ + ["path", { d: "m10 18 3-3-3-3", key: "18f6ys" }], + ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }], + [ + "path", + { + d: "M4 11V4a2 2 0 0 1 2-2h9l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h7", + key: "50q2rw" + } + ] +]; +var FileSymlink = createLucideIcon("file-symlink", __iconNode23); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/file-text.js +var __iconNode24 = [ + ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }], + ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }], + ["path", { d: "M10 9H8", key: "b1mrlr" }], + ["path", { d: "M16 13H8", key: "t4e002" }], + ["path", { d: "M16 17H8", key: "z1uh3a" }] +]; +var FileText = createLucideIcon("file-text", __iconNode24); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/file.js +var __iconNode25 = [ + ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }], + ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }] +]; +var File = createLucideIcon("file", __iconNode25); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/folder.js +var __iconNode26 = [ + [ + "path", + { + d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z", + key: "1kt360" + } + ] +]; +var Folder = createLucideIcon("folder", __iconNode26); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/image.js +var __iconNode27 = [ + ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }], + ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }], + ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }] +]; +var Image = createLucideIcon("image", __iconNode27); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/info.js +var __iconNode28 = [ + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], + ["path", { d: "M12 16v-4", key: "1dtifu" }], + ["path", { d: "M12 8h.01", key: "e9boi3" }] +]; +var Info = createLucideIcon("info", __iconNode28); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/message-circle-question.js +var __iconNode29 = [ + ["path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z", key: "vv11sd" }], + ["path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3", key: "1u773s" }], + ["path", { d: "M12 17h.01", key: "p32p05" }] +]; +var MessageCircleQuestion = createLucideIcon("message-circle-question", __iconNode29); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/pencil.js +var __iconNode30 = [ + [ + "path", + { + d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z", + key: "1a8usu" + } + ], + ["path", { d: "m15 5 4 4", key: "1mk7zo" }] +]; +var Pencil = createLucideIcon("pencil", __iconNode30); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/play.js +var __iconNode31 = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]]; +var Play = createLucideIcon("play", __iconNode31); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/rotate-ccw.js +var __iconNode32 = [ + ["path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", key: "1357e3" }], + ["path", { d: "M3 3v5h5", key: "1xhq8a" }] +]; +var RotateCcw = createLucideIcon("rotate-ccw", __iconNode32); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/square.js +var __iconNode33 = [ + ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }] +]; +var Square = createLucideIcon("square", __iconNode33); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/text.js +var __iconNode34 = [ + ["path", { d: "M15 18H3", key: "olowqp" }], + ["path", { d: "M17 6H3", key: "16j9eg" }], + ["path", { d: "M21 12H3", key: "2avoz0" }] +]; +var Text = createLucideIcon("text", __iconNode34); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/trash-2.js +var __iconNode35 = [ + ["path", { d: "M3 6h18", key: "d0wm0j" }], + ["path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6", key: "4alrt4" }], + ["path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2", key: "v07s0e" }], + ["line", { x1: "10", x2: "10", y1: "11", y2: "17", key: "1uufr5" }], + ["line", { x1: "14", x2: "14", y1: "11", y2: "17", key: "xtxkd" }] +]; +var Trash2 = createLucideIcon("trash-2", __iconNode35); + +// ../../../../../../../node_modules/lucide-react/dist/esm/icons/triangle-alert.js +var __iconNode36 = [ + [ + "path", + { + d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3", + key: "wmoenq" + } + ], + ["path", { d: "M12 9v4", key: "juzpu7" }], + ["path", { d: "M12 17h.01", key: "p32p05" }] +]; +var TriangleAlert = createLucideIcon("triangle-alert", __iconNode36); +/*! Bundled license information: + +lucide-react/dist/esm/shared/src/utils.js: +lucide-react/dist/esm/defaultAttributes.js: +lucide-react/dist/esm/Icon.js: +lucide-react/dist/esm/createLucideIcon.js: +lucide-react/dist/esm/icons/asterisk.js: +lucide-react/dist/esm/icons/check.js: +lucide-react/dist/esm/icons/chevron-right.js: +lucide-react/dist/esm/icons/ellipsis-vertical.js: +lucide-react/dist/esm/icons/loader-circle.js: +lucide-react/dist/esm/icons/lock.js: +lucide-react/dist/esm/icons/move-down.js: +lucide-react/dist/esm/icons/move-left.js: +lucide-react/dist/esm/icons/move-right.js: +lucide-react/dist/esm/icons/move-up.js: +lucide-react/dist/esm/icons/plus.js: +lucide-react/dist/esm/icons/refresh-cw.js: +lucide-react/dist/esm/icons/x.js: +lucide-react/dist/esm/icons/ban.js: +lucide-react/dist/esm/icons/chevron-down.js: +lucide-react/dist/esm/icons/chevron-left.js: +lucide-react/dist/esm/icons/chevron-up.js: +lucide-react/dist/esm/icons/circle-alert.js: +lucide-react/dist/esm/icons/circle-ellipsis.js: +lucide-react/dist/esm/icons/circle-plus.js: +lucide-react/dist/esm/icons/copy.js: +lucide-react/dist/esm/icons/dot.js: +lucide-react/dist/esm/icons/file-symlink.js: +lucide-react/dist/esm/icons/file-text.js: +lucide-react/dist/esm/icons/file.js: +lucide-react/dist/esm/icons/folder.js: +lucide-react/dist/esm/icons/image.js: +lucide-react/dist/esm/icons/info.js: +lucide-react/dist/esm/icons/message-circle-question.js: +lucide-react/dist/esm/icons/pencil.js: +lucide-react/dist/esm/icons/play.js: +lucide-react/dist/esm/icons/rotate-ccw.js: +lucide-react/dist/esm/icons/square.js: +lucide-react/dist/esm/icons/text.js: +lucide-react/dist/esm/icons/trash-2.js: +lucide-react/dist/esm/icons/triangle-alert.js: +lucide-react/dist/esm/lucide-react.js: + (** + * @license lucide-react v0.503.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + *) +*/ + +export { Asterisk, Ban, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, CircleAlert, CircleEllipsis, CirclePlus, Copy, Dot, EllipsisVertical, File, FileSymlink, FileText, Folder, Image, Info, LoaderCircle, Lock, MessageCircleQuestion, MoveDown, MoveLeft, MoveRight, MoveUp, Pencil, Play, Plus, RefreshCw, RotateCcw, Square, Text, Trash2, TriangleAlert, X }; diff --git a/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-RJP66NWB.js b/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-RJP66NWB.js new file mode 100644 index 00000000000..c826294934b --- /dev/null +++ b/src/out/vs/workbench/contrib/cortexide/browser/react/out/chunk-RJP66NWB.js @@ -0,0 +1,22073 @@ +import { __commonJS, __toESM } from './chunk-JSBRDJBE.js'; +import { DisposableStore } from 'vs/base/common/lifecycle.js'; +import { ColorScheme } from 'vs/platform/theme/common/theme.js'; +import { IExplorerService } from 'vs/workbench/contrib/files/browser/files.js'; +import { IModelService } from 'vs/editor/common/services/model.js'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService.js'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView.js'; +import { IFileService } from 'vs/platform/files/common/files.js'; +import { IHoverService } from 'vs/platform/hover/browser/hover.js'; +import { IThemeService } from 'vs/platform/theme/common/themeService.js'; +import { ILLMMessageService } from 'vs/workbench/contrib/cortexide/common/sendLLMMessageService.js'; +import { IRefreshModelService } from 'vs/workbench/contrib/cortexide/common/refreshModelService.js'; +import { ICortexideSettingsService } from 'vs/workbench/contrib/cortexide/common/cortexideSettingsService.js'; +import { IExtensionTransferService } from 'vs/workbench/contrib/cortexide/browser/extensionTransferService.js'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation.js'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService.js'; +import { ICommandService } from 'vs/platform/commands/common/commands.js'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey.js'; +import { INotificationService } from 'vs/platform/notification/common/notification.js'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility.js'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry.js'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures.js'; +import { ILanguageDetectionService } from 'vs/services/languageDetection/common/languageDetectionWorkerService.js'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding.js'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment.js'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration.js'; +import { IPathService } from 'vs/workbench/services/path/common/pathService.js'; +import { IMetricsService } from 'vs/workbench/contrib/cortexide/common/metricsService.js'; +import { IChatThreadService } from 'vs/workbench/contrib/cortexide/chatThreadService.js'; +import { ITerminalToolService } from 'vs/workbench/contrib/cortexide/browser/terminalToolService.js'; +import { ILanguageService } from 'vs/editor/common/languages/language.js'; +import { ICortexideModelService } from 'vs/workbench/contrib/cortexide/common/cortexideModelService.js'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace.js'; +import { ICortexideCommandBarService } from 'vs/workbench/contrib/cortexide/cortexideCommandBarService.js'; +import { INativeHostService } from 'vs/platform/native/common/native.js'; +import { IEditCodeService } from 'vs/workbench/contrib/cortexide/editCodeServiceInterface.js'; +import { IToolsService } from 'vs/workbench/contrib/cortexide/toolsService.js'; +import { IConvertToLLMMessageService } from 'vs/workbench/contrib/cortexide/convertToLLMMessageService.js'; +import { ITerminalService } from 'vs/workbench/terminal/browser/terminal.js'; +import { ISearchService } from 'vs/services/search/common/search.js'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement.js'; +import { IMCPService } from 'vs/workbench/contrib/cortexide/common/mcpService.js'; +import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage.js'; +import { OPT_OUT_KEY } from 'vs/workbench/contrib/cortexide/common/storageKeys.js'; +import { IRepoIndexerService } from 'vs/workbench/contrib/cortexide/repoIndexerService.js'; +import { ISecretDetectionService } from 'vs/workbench/contrib/cortexide/common/secretDetectionService.js'; + +// ../../../../../../../node_modules/scheduler/cjs/scheduler.development.js +var require_scheduler_development = __commonJS({ + "../../../../../../../node_modules/scheduler/cjs/scheduler.development.js"(exports$1) { + (function() { + function performWorkUntilDeadline() { + needsPaint = false; + if (isMessageLoopRunning) { + var currentTime = exports$1.unstable_now(); + startTime = currentTime; + var hasMoreWork = true; + try { + a: { + isHostCallbackScheduled = false; + isHostTimeoutScheduled && (isHostTimeoutScheduled = false, localClearTimeout(taskTimeoutID), taskTimeoutID = -1); + isPerformingWork = true; + var previousPriorityLevel = currentPriorityLevel; + try { + b: { + advanceTimers(currentTime); + for (currentTask = peek(taskQueue); null !== currentTask && !(currentTask.expirationTime > currentTime && shouldYieldToHost()); ) { + var callback = currentTask.callback; + if ("function" === typeof callback) { + currentTask.callback = null; + currentPriorityLevel = currentTask.priorityLevel; + var continuationCallback = callback( + currentTask.expirationTime <= currentTime + ); + currentTime = exports$1.unstable_now(); + if ("function" === typeof continuationCallback) { + currentTask.callback = continuationCallback; + advanceTimers(currentTime); + hasMoreWork = true; + break b; + } + currentTask === peek(taskQueue) && pop(taskQueue); + advanceTimers(currentTime); + } else pop(taskQueue); + currentTask = peek(taskQueue); + } + if (null !== currentTask) hasMoreWork = true; + else { + var firstTimer = peek(timerQueue); + null !== firstTimer && requestHostTimeout( + handleTimeout, + firstTimer.startTime - currentTime + ); + hasMoreWork = false; + } + } + break a; + } finally { + currentTask = null, currentPriorityLevel = previousPriorityLevel, isPerformingWork = false; + } + hasMoreWork = void 0; + } + } finally { + hasMoreWork ? schedulePerformWorkUntilDeadline() : isMessageLoopRunning = false; + } + } + } + function push(heap, node) { + var index = heap.length; + heap.push(node); + a: for (; 0 < index; ) { + var parentIndex = index - 1 >>> 1, parent = heap[parentIndex]; + if (0 < compare(parent, node)) + heap[parentIndex] = node, heap[index] = parent, index = parentIndex; + else break a; + } + } + function peek(heap) { + return 0 === heap.length ? null : heap[0]; + } + function pop(heap) { + if (0 === heap.length) return null; + var first = heap[0], last = heap.pop(); + if (last !== first) { + heap[0] = last; + a: for (var index = 0, length = heap.length, halfLength = length >>> 1; index < halfLength; ) { + var leftIndex = 2 * (index + 1) - 1, left = heap[leftIndex], rightIndex = leftIndex + 1, right = heap[rightIndex]; + if (0 > compare(left, last)) + rightIndex < length && 0 > compare(right, left) ? (heap[index] = right, heap[rightIndex] = last, index = rightIndex) : (heap[index] = left, heap[leftIndex] = last, index = leftIndex); + else if (rightIndex < length && 0 > compare(right, last)) + heap[index] = right, heap[rightIndex] = last, index = rightIndex; + else break a; + } + } + return first; + } + function compare(a, b) { + var diff = a.sortIndex - b.sortIndex; + return 0 !== diff ? diff : a.id - b.id; + } + function advanceTimers(currentTime) { + for (var timer = peek(timerQueue); null !== timer; ) { + if (null === timer.callback) pop(timerQueue); + else if (timer.startTime <= currentTime) + pop(timerQueue), timer.sortIndex = timer.expirationTime, push(taskQueue, timer); + else break; + timer = peek(timerQueue); + } + } + function handleTimeout(currentTime) { + isHostTimeoutScheduled = false; + advanceTimers(currentTime); + if (!isHostCallbackScheduled) + if (null !== peek(taskQueue)) + isHostCallbackScheduled = true, isMessageLoopRunning || (isMessageLoopRunning = true, schedulePerformWorkUntilDeadline()); + else { + var firstTimer = peek(timerQueue); + null !== firstTimer && requestHostTimeout( + handleTimeout, + firstTimer.startTime - currentTime + ); + } + } + function shouldYieldToHost() { + return needsPaint ? true : exports$1.unstable_now() - startTime < frameInterval ? false : true; + } + function requestHostTimeout(callback, ms) { + taskTimeoutID = localSetTimeout(function() { + callback(exports$1.unstable_now()); + }, ms); + } + "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error()); + exports$1.unstable_now = void 0; + if ("object" === typeof performance && "function" === typeof performance.now) { + var localPerformance = performance; + exports$1.unstable_now = function() { + return localPerformance.now(); + }; + } else { + var localDate = Date, initialTime = localDate.now(); + exports$1.unstable_now = function() { + return localDate.now() - initialTime; + }; + } + var taskQueue = [], timerQueue = [], taskIdCounter = 1, currentTask = null, currentPriorityLevel = 3, isPerformingWork = false, isHostCallbackScheduled = false, isHostTimeoutScheduled = false, needsPaint = false, localSetTimeout = "function" === typeof setTimeout ? setTimeout : null, localClearTimeout = "function" === typeof clearTimeout ? clearTimeout : null, localSetImmediate = "undefined" !== typeof setImmediate ? setImmediate : null, isMessageLoopRunning = false, taskTimeoutID = -1, frameInterval = 5, startTime = -1; + if ("function" === typeof localSetImmediate) + var schedulePerformWorkUntilDeadline = function() { + localSetImmediate(performWorkUntilDeadline); + }; + else if ("undefined" !== typeof MessageChannel) { + var channel = new MessageChannel(), port = channel.port2; + channel.port1.onmessage = performWorkUntilDeadline; + schedulePerformWorkUntilDeadline = function() { + port.postMessage(null); + }; + } else + schedulePerformWorkUntilDeadline = function() { + localSetTimeout(performWorkUntilDeadline, 0); + }; + exports$1.unstable_IdlePriority = 5; + exports$1.unstable_ImmediatePriority = 1; + exports$1.unstable_LowPriority = 4; + exports$1.unstable_NormalPriority = 3; + exports$1.unstable_Profiling = null; + exports$1.unstable_UserBlockingPriority = 2; + exports$1.unstable_cancelCallback = function(task) { + task.callback = null; + }; + exports$1.unstable_forceFrameRate = function(fps) { + 0 > fps || 125 < fps ? console.error( + "forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported" + ) : frameInterval = 0 < fps ? Math.floor(1e3 / fps) : 5; + }; + exports$1.unstable_getCurrentPriorityLevel = function() { + return currentPriorityLevel; + }; + exports$1.unstable_next = function(eventHandler) { + switch (currentPriorityLevel) { + case 1: + case 2: + case 3: + var priorityLevel = 3; + break; + default: + priorityLevel = currentPriorityLevel; + } + var previousPriorityLevel = currentPriorityLevel; + currentPriorityLevel = priorityLevel; + try { + return eventHandler(); + } finally { + currentPriorityLevel = previousPriorityLevel; + } + }; + exports$1.unstable_requestPaint = function() { + needsPaint = true; + }; + exports$1.unstable_runWithPriority = function(priorityLevel, eventHandler) { + switch (priorityLevel) { + case 1: + case 2: + case 3: + case 4: + case 5: + break; + default: + priorityLevel = 3; + } + var previousPriorityLevel = currentPriorityLevel; + currentPriorityLevel = priorityLevel; + try { + return eventHandler(); + } finally { + currentPriorityLevel = previousPriorityLevel; + } + }; + exports$1.unstable_scheduleCallback = function(priorityLevel, callback, options) { + var currentTime = exports$1.unstable_now(); + "object" === typeof options && null !== options ? (options = options.delay, options = "number" === typeof options && 0 < options ? currentTime + options : currentTime) : options = currentTime; + switch (priorityLevel) { + case 1: + var timeout = -1; + break; + case 2: + timeout = 250; + break; + case 5: + timeout = 1073741823; + break; + case 4: + timeout = 1e4; + break; + default: + timeout = 5e3; + } + timeout = options + timeout; + priorityLevel = { + id: taskIdCounter++, + callback, + priorityLevel, + startTime: options, + expirationTime: timeout, + sortIndex: -1 + }; + options > currentTime ? (priorityLevel.sortIndex = options, push(timerQueue, priorityLevel), null === peek(taskQueue) && priorityLevel === peek(timerQueue) && (isHostTimeoutScheduled ? (localClearTimeout(taskTimeoutID), taskTimeoutID = -1) : isHostTimeoutScheduled = true, requestHostTimeout(handleTimeout, options - currentTime))) : (priorityLevel.sortIndex = timeout, push(taskQueue, priorityLevel), isHostCallbackScheduled || isPerformingWork || (isHostCallbackScheduled = true, isMessageLoopRunning || (isMessageLoopRunning = true, schedulePerformWorkUntilDeadline()))); + return priorityLevel; + }; + exports$1.unstable_shouldYield = shouldYieldToHost; + exports$1.unstable_wrapCallback = function(callback) { + var parentPriorityLevel = currentPriorityLevel; + return function() { + var previousPriorityLevel = currentPriorityLevel; + currentPriorityLevel = parentPriorityLevel; + try { + return callback.apply(this, arguments); + } finally { + currentPriorityLevel = previousPriorityLevel; + } + }; + }; + "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error()); + })(); + } +}); + +// ../../../../../../../node_modules/scheduler/index.js +var require_scheduler = __commonJS({ + "../../../../../../../node_modules/scheduler/index.js"(exports$1, module) { + { + module.exports = require_scheduler_development(); + } + } +}); + +// ../../../../../../../node_modules/react/cjs/react.development.js +var require_react_development = __commonJS({ + "../../../../../../../node_modules/react/cjs/react.development.js"(exports$1, module) { + (function() { + function defineDeprecationWarning(methodName, info) { + Object.defineProperty(Component.prototype, methodName, { + get: function() { + console.warn( + "%s(...) is deprecated in plain JavaScript React classes. %s", + info[0], + info[1] + ); + } + }); + } + function getIteratorFn(maybeIterable) { + if (null === maybeIterable || "object" !== typeof maybeIterable) + return null; + maybeIterable = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable["@@iterator"]; + return "function" === typeof maybeIterable ? maybeIterable : null; + } + function warnNoop(publicInstance, callerName) { + publicInstance = (publicInstance = publicInstance.constructor) && (publicInstance.displayName || publicInstance.name) || "ReactClass"; + var warningKey = publicInstance + "." + callerName; + didWarnStateUpdateForUnmountedComponent[warningKey] || (console.error( + "Can't call %s on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the %s component.", + callerName, + publicInstance + ), didWarnStateUpdateForUnmountedComponent[warningKey] = true); + } + function Component(props, context, updater) { + this.props = props; + this.context = context; + this.refs = emptyObject; + this.updater = updater || ReactNoopUpdateQueue; + } + function ComponentDummy() { + } + function PureComponent(props, context, updater) { + this.props = props; + this.context = context; + this.refs = emptyObject; + this.updater = updater || ReactNoopUpdateQueue; + } + function noop() { + } + function testStringCoercion(value) { + return "" + value; + } + function checkKeyStringCoercion(value) { + try { + testStringCoercion(value); + var JSCompiler_inline_result = false; + } catch (e) { + JSCompiler_inline_result = true; + } + if (JSCompiler_inline_result) { + JSCompiler_inline_result = console; + var JSCompiler_temp_const = JSCompiler_inline_result.error; + var JSCompiler_inline_result$jscomp$0 = "function" === typeof Symbol && Symbol.toStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object"; + JSCompiler_temp_const.call( + JSCompiler_inline_result, + "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.", + JSCompiler_inline_result$jscomp$0 + ); + return testStringCoercion(value); + } + } + function getComponentNameFromType(type) { + if (null == type) return null; + if ("function" === typeof type) + return type.$$typeof === REACT_CLIENT_REFERENCE ? null : type.displayName || type.name || null; + if ("string" === typeof type) return type; + switch (type) { + case REACT_FRAGMENT_TYPE: + return "Fragment"; + case REACT_PROFILER_TYPE: + return "Profiler"; + case REACT_STRICT_MODE_TYPE: + return "StrictMode"; + case REACT_SUSPENSE_TYPE: + return "Suspense"; + case REACT_SUSPENSE_LIST_TYPE: + return "SuspenseList"; + case REACT_ACTIVITY_TYPE: + return "Activity"; + } + if ("object" === typeof type) + switch ("number" === typeof type.tag && console.error( + "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue." + ), type.$$typeof) { + case REACT_PORTAL_TYPE: + return "Portal"; + case REACT_CONTEXT_TYPE: + return type.displayName || "Context"; + case REACT_CONSUMER_TYPE: + return (type._context.displayName || "Context") + ".Consumer"; + case REACT_FORWARD_REF_TYPE: + var innerType = type.render; + type = type.displayName; + type || (type = innerType.displayName || innerType.name || "", type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef"); + return type; + case REACT_MEMO_TYPE: + return innerType = type.displayName || null, null !== innerType ? innerType : getComponentNameFromType(type.type) || "Memo"; + case REACT_LAZY_TYPE: + innerType = type._payload; + type = type._init; + try { + return getComponentNameFromType(type(innerType)); + } catch (x) { + } + } + return null; + } + function getTaskName(type) { + if (type === REACT_FRAGMENT_TYPE) return "<>"; + if ("object" === typeof type && null !== type && type.$$typeof === REACT_LAZY_TYPE) + return "<...>"; + try { + var name = getComponentNameFromType(type); + return name ? "<" + name + ">" : "<...>"; + } catch (x) { + return "<...>"; + } + } + function getOwner() { + var dispatcher = ReactSharedInternals.A; + return null === dispatcher ? null : dispatcher.getOwner(); + } + function UnknownOwner() { + return Error("react-stack-top-frame"); + } + function hasValidKey(config) { + if (hasOwnProperty.call(config, "key")) { + var getter = Object.getOwnPropertyDescriptor(config, "key").get; + if (getter && getter.isReactWarning) return false; + } + return void 0 !== config.key; + } + function defineKeyPropWarningGetter(props, displayName) { + function warnAboutAccessingKey() { + specialPropKeyWarningShown || (specialPropKeyWarningShown = true, console.error( + "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)", + displayName + )); + } + warnAboutAccessingKey.isReactWarning = true; + Object.defineProperty(props, "key", { + get: warnAboutAccessingKey, + configurable: true + }); + } + function elementRefGetterWithDeprecationWarning() { + var componentName = getComponentNameFromType(this.type); + didWarnAboutElementRef[componentName] || (didWarnAboutElementRef[componentName] = true, console.error( + "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release." + )); + componentName = this.props.ref; + return void 0 !== componentName ? componentName : null; + } + function ReactElement(type, key, props, owner, debugStack, debugTask) { + var refProp = props.ref; + type = { + $$typeof: REACT_ELEMENT_TYPE, + type, + key, + props, + _owner: owner + }; + null !== (void 0 !== refProp ? refProp : null) ? Object.defineProperty(type, "ref", { + enumerable: false, + get: elementRefGetterWithDeprecationWarning + }) : Object.defineProperty(type, "ref", { enumerable: false, value: null }); + type._store = {}; + Object.defineProperty(type._store, "validated", { + configurable: false, + enumerable: false, + writable: true, + value: 0 + }); + Object.defineProperty(type, "_debugInfo", { + configurable: false, + enumerable: false, + writable: true, + value: null + }); + Object.defineProperty(type, "_debugStack", { + configurable: false, + enumerable: false, + writable: true, + value: debugStack + }); + Object.defineProperty(type, "_debugTask", { + configurable: false, + enumerable: false, + writable: true, + value: debugTask + }); + Object.freeze && (Object.freeze(type.props), Object.freeze(type)); + return type; + } + function cloneAndReplaceKey(oldElement, newKey) { + newKey = ReactElement( + oldElement.type, + newKey, + oldElement.props, + oldElement._owner, + oldElement._debugStack, + oldElement._debugTask + ); + oldElement._store && (newKey._store.validated = oldElement._store.validated); + return newKey; + } + function validateChildKeys(node) { + isValidElement(node) ? node._store && (node._store.validated = 1) : "object" === typeof node && null !== node && node.$$typeof === REACT_LAZY_TYPE && ("fulfilled" === node._payload.status ? isValidElement(node._payload.value) && node._payload.value._store && (node._payload.value._store.validated = 1) : node._store && (node._store.validated = 1)); + } + function isValidElement(object) { + return "object" === typeof object && null !== object && object.$$typeof === REACT_ELEMENT_TYPE; + } + function escape(key) { + var escaperLookup = { "=": "=0", ":": "=2" }; + return "$" + key.replace(/[=:]/g, function(match) { + return escaperLookup[match]; + }); + } + function getElementKey(element, index) { + return "object" === typeof element && null !== element && null != element.key ? (checkKeyStringCoercion(element.key), escape("" + element.key)) : index.toString(36); + } + function resolveThenable(thenable) { + switch (thenable.status) { + case "fulfilled": + return thenable.value; + case "rejected": + throw thenable.reason; + default: + switch ("string" === typeof thenable.status ? thenable.then(noop, noop) : (thenable.status = "pending", thenable.then( + function(fulfilledValue) { + "pending" === thenable.status && (thenable.status = "fulfilled", thenable.value = fulfilledValue); + }, + function(error) { + "pending" === thenable.status && (thenable.status = "rejected", thenable.reason = error); + } + )), thenable.status) { + case "fulfilled": + return thenable.value; + case "rejected": + throw thenable.reason; + } + } + throw thenable; + } + function mapIntoArray(children, array, escapedPrefix, nameSoFar, callback) { + var type = typeof children; + if ("undefined" === type || "boolean" === type) children = null; + var invokeCallback = false; + if (null === children) invokeCallback = true; + else + switch (type) { + case "bigint": + case "string": + case "number": + invokeCallback = true; + break; + case "object": + switch (children.$$typeof) { + case REACT_ELEMENT_TYPE: + case REACT_PORTAL_TYPE: + invokeCallback = true; + break; + case REACT_LAZY_TYPE: + return invokeCallback = children._init, mapIntoArray( + invokeCallback(children._payload), + array, + escapedPrefix, + nameSoFar, + callback + ); + } + } + if (invokeCallback) { + invokeCallback = children; + callback = callback(invokeCallback); + var childKey = "" === nameSoFar ? "." + getElementKey(invokeCallback, 0) : nameSoFar; + isArrayImpl(callback) ? (escapedPrefix = "", null != childKey && (escapedPrefix = childKey.replace(userProvidedKeyEscapeRegex, "$&/") + "/"), mapIntoArray(callback, array, escapedPrefix, "", function(c) { + return c; + })) : null != callback && (isValidElement(callback) && (null != callback.key && (invokeCallback && invokeCallback.key === callback.key || checkKeyStringCoercion(callback.key)), escapedPrefix = cloneAndReplaceKey( + callback, + escapedPrefix + (null == callback.key || invokeCallback && invokeCallback.key === callback.key ? "" : ("" + callback.key).replace( + userProvidedKeyEscapeRegex, + "$&/" + ) + "/") + childKey + ), "" !== nameSoFar && null != invokeCallback && isValidElement(invokeCallback) && null == invokeCallback.key && invokeCallback._store && !invokeCallback._store.validated && (escapedPrefix._store.validated = 2), callback = escapedPrefix), array.push(callback)); + return 1; + } + invokeCallback = 0; + childKey = "" === nameSoFar ? "." : nameSoFar + ":"; + if (isArrayImpl(children)) + for (var i = 0; i < children.length; i++) + nameSoFar = children[i], type = childKey + getElementKey(nameSoFar, i), invokeCallback += mapIntoArray( + nameSoFar, + array, + escapedPrefix, + type, + callback + ); + else if (i = getIteratorFn(children), "function" === typeof i) + for (i === children.entries && (didWarnAboutMaps || console.warn( + "Using Maps as children is not supported. Use an array of keyed ReactElements instead." + ), didWarnAboutMaps = true), children = i.call(children), i = 0; !(nameSoFar = children.next()).done; ) + nameSoFar = nameSoFar.value, type = childKey + getElementKey(nameSoFar, i++), invokeCallback += mapIntoArray( + nameSoFar, + array, + escapedPrefix, + type, + callback + ); + else if ("object" === type) { + if ("function" === typeof children.then) + return mapIntoArray( + resolveThenable(children), + array, + escapedPrefix, + nameSoFar, + callback + ); + array = String(children); + throw Error( + "Objects are not valid as a React child (found: " + ("[object Object]" === array ? "object with keys {" + Object.keys(children).join(", ") + "}" : array) + "). If you meant to render a collection of children, use an array instead." + ); + } + return invokeCallback; + } + function mapChildren(children, func, context) { + if (null == children) return children; + var result = [], count = 0; + mapIntoArray(children, result, "", "", function(child) { + return func.call(context, child, count++); + }); + return result; + } + function lazyInitializer(payload) { + if (-1 === payload._status) { + var ioInfo = payload._ioInfo; + null != ioInfo && (ioInfo.start = ioInfo.end = performance.now()); + ioInfo = payload._result; + var thenable = ioInfo(); + thenable.then( + function(moduleObject) { + if (0 === payload._status || -1 === payload._status) { + payload._status = 1; + payload._result = moduleObject; + var _ioInfo = payload._ioInfo; + null != _ioInfo && (_ioInfo.end = performance.now()); + void 0 === thenable.status && (thenable.status = "fulfilled", thenable.value = moduleObject); + } + }, + function(error) { + if (0 === payload._status || -1 === payload._status) { + payload._status = 2; + payload._result = error; + var _ioInfo2 = payload._ioInfo; + null != _ioInfo2 && (_ioInfo2.end = performance.now()); + void 0 === thenable.status && (thenable.status = "rejected", thenable.reason = error); + } + } + ); + ioInfo = payload._ioInfo; + if (null != ioInfo) { + ioInfo.value = thenable; + var displayName = thenable.displayName; + "string" === typeof displayName && (ioInfo.name = displayName); + } + -1 === payload._status && (payload._status = 0, payload._result = thenable); + } + if (1 === payload._status) + return ioInfo = payload._result, void 0 === ioInfo && console.error( + "lazy: Expected the result of a dynamic import() call. Instead received: %s\n\nYour code should look like: \n const MyComponent = lazy(() => import('./MyComponent'))\n\nDid you accidentally put curly braces around the import?", + ioInfo + ), "default" in ioInfo || console.error( + "lazy: Expected the result of a dynamic import() call. Instead received: %s\n\nYour code should look like: \n const MyComponent = lazy(() => import('./MyComponent'))", + ioInfo + ), ioInfo.default; + throw payload._result; + } + function resolveDispatcher() { + var dispatcher = ReactSharedInternals.H; + null === dispatcher && console.error( + "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem." + ); + return dispatcher; + } + function releaseAsyncTransition() { + ReactSharedInternals.asyncTransitions--; + } + function enqueueTask(task) { + if (null === enqueueTaskImpl) + try { + var requireString = ("require" + Math.random()).slice(0, 7); + enqueueTaskImpl = (module && module[requireString]).call( + module, + "timers" + ).setImmediate; + } catch (_err) { + enqueueTaskImpl = function(callback) { + false === didWarnAboutMessageChannel && (didWarnAboutMessageChannel = true, "undefined" === typeof MessageChannel && console.error( + "This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning." + )); + var channel = new MessageChannel(); + channel.port1.onmessage = callback; + channel.port2.postMessage(void 0); + }; + } + return enqueueTaskImpl(task); + } + function aggregateErrors(errors) { + return 1 < errors.length && "function" === typeof AggregateError ? new AggregateError(errors) : errors[0]; + } + function popActScope(prevActQueue, prevActScopeDepth) { + prevActScopeDepth !== actScopeDepth - 1 && console.error( + "You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. " + ); + actScopeDepth = prevActScopeDepth; + } + function recursivelyFlushAsyncActWork(returnValue, resolve, reject) { + var queue = ReactSharedInternals.actQueue; + if (null !== queue) + if (0 !== queue.length) + try { + flushActQueue(queue); + enqueueTask(function() { + return recursivelyFlushAsyncActWork(returnValue, resolve, reject); + }); + return; + } catch (error) { + ReactSharedInternals.thrownErrors.push(error); + } + else ReactSharedInternals.actQueue = null; + 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve(returnValue); + } + function flushActQueue(queue) { + if (!isFlushing) { + isFlushing = true; + var i = 0; + try { + for (; i < queue.length; i++) { + var callback = queue[i]; + do { + ReactSharedInternals.didUsePromise = false; + var continuation = callback(false); + if (null !== continuation) { + if (ReactSharedInternals.didUsePromise) { + queue[i] = callback; + queue.splice(0, i); + return; + } + callback = continuation; + } else break; + } while (1); + } + queue.length = 0; + } catch (error) { + queue.splice(0, i + 1), ReactSharedInternals.thrownErrors.push(error); + } finally { + isFlushing = false; + } + } + } + "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error()); + var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), MAYBE_ITERATOR_SYMBOL = Symbol.iterator, didWarnStateUpdateForUnmountedComponent = {}, ReactNoopUpdateQueue = { + isMounted: function() { + return false; + }, + enqueueForceUpdate: function(publicInstance) { + warnNoop(publicInstance, "forceUpdate"); + }, + enqueueReplaceState: function(publicInstance) { + warnNoop(publicInstance, "replaceState"); + }, + enqueueSetState: function(publicInstance) { + warnNoop(publicInstance, "setState"); + } + }, assign = Object.assign, emptyObject = {}; + Object.freeze(emptyObject); + Component.prototype.isReactComponent = {}; + Component.prototype.setState = function(partialState, callback) { + if ("object" !== typeof partialState && "function" !== typeof partialState && null != partialState) + throw Error( + "takes an object of state variables to update or a function which returns an object of state variables." + ); + this.updater.enqueueSetState(this, partialState, callback, "setState"); + }; + Component.prototype.forceUpdate = function(callback) { + this.updater.enqueueForceUpdate(this, callback, "forceUpdate"); + }; + var deprecatedAPIs = { + isMounted: [ + "isMounted", + "Instead, make sure to clean up subscriptions and pending requests in componentWillUnmount to prevent memory leaks." + ], + replaceState: [ + "replaceState", + "Refactor your code to use setState instead (see https://github.com/facebook/react/issues/3236)." + ] + }; + for (fnName in deprecatedAPIs) + deprecatedAPIs.hasOwnProperty(fnName) && defineDeprecationWarning(fnName, deprecatedAPIs[fnName]); + ComponentDummy.prototype = Component.prototype; + deprecatedAPIs = PureComponent.prototype = new ComponentDummy(); + deprecatedAPIs.constructor = PureComponent; + assign(deprecatedAPIs, Component.prototype); + deprecatedAPIs.isPureReactComponent = true; + var isArrayImpl = Array.isArray, REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = { + H: null, + A: null, + T: null, + S: null, + actQueue: null, + asyncTransitions: 0, + isBatchingLegacy: false, + didScheduleLegacyUpdate: false, + didUsePromise: false, + thrownErrors: [], + getCurrentStack: null, + recentlyCreatedOwnerStacks: 0 + }, hasOwnProperty = Object.prototype.hasOwnProperty, createTask = console.createTask ? console.createTask : function() { + return null; + }; + deprecatedAPIs = { + react_stack_bottom_frame: function(callStackForError) { + return callStackForError(); + } + }; + var specialPropKeyWarningShown, didWarnAboutOldJSXRuntime; + var didWarnAboutElementRef = {}; + var unknownOwnerDebugStack = deprecatedAPIs.react_stack_bottom_frame.bind( + deprecatedAPIs, + UnknownOwner + )(); + var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner)); + var didWarnAboutMaps = false, userProvidedKeyEscapeRegex = /\/+/g, reportGlobalError = "function" === typeof reportError ? reportError : function(error) { + if ("object" === typeof window && "function" === typeof window.ErrorEvent) { + var event = new window.ErrorEvent("error", { + bubbles: true, + cancelable: true, + message: "object" === typeof error && null !== error && "string" === typeof error.message ? String(error.message) : String(error), + error + }); + if (!window.dispatchEvent(event)) return; + } else if ("object" === typeof process && "function" === typeof process.emit) { + process.emit("uncaughtException", error); + return; + } + console.error(error); + }, didWarnAboutMessageChannel = false, enqueueTaskImpl = null, actScopeDepth = 0, didWarnNoAwaitAct = false, isFlushing = false, queueSeveralMicrotasks = "function" === typeof queueMicrotask ? function(callback) { + queueMicrotask(function() { + return queueMicrotask(callback); + }); + } : enqueueTask; + deprecatedAPIs = Object.freeze({ + __proto__: null, + c: function(size) { + return resolveDispatcher().useMemoCache(size); + } + }); + var fnName = { + map: mapChildren, + forEach: function(children, forEachFunc, forEachContext) { + mapChildren( + children, + function() { + forEachFunc.apply(this, arguments); + }, + forEachContext + ); + }, + count: function(children) { + var n = 0; + mapChildren(children, function() { + n++; + }); + return n; + }, + toArray: function(children) { + return mapChildren(children, function(child) { + return child; + }) || []; + }, + only: function(children) { + if (!isValidElement(children)) + throw Error( + "React.Children.only expected to receive a single React element child." + ); + return children; + } + }; + exports$1.Activity = REACT_ACTIVITY_TYPE; + exports$1.Children = fnName; + exports$1.Component = Component; + exports$1.Fragment = REACT_FRAGMENT_TYPE; + exports$1.Profiler = REACT_PROFILER_TYPE; + exports$1.PureComponent = PureComponent; + exports$1.StrictMode = REACT_STRICT_MODE_TYPE; + exports$1.Suspense = REACT_SUSPENSE_TYPE; + exports$1.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = ReactSharedInternals; + exports$1.__COMPILER_RUNTIME = deprecatedAPIs; + exports$1.act = function(callback) { + var prevActQueue = ReactSharedInternals.actQueue, prevActScopeDepth = actScopeDepth; + actScopeDepth++; + var queue = ReactSharedInternals.actQueue = null !== prevActQueue ? prevActQueue : [], didAwaitActCall = false; + try { + var result = callback(); + } catch (error) { + ReactSharedInternals.thrownErrors.push(error); + } + if (0 < ReactSharedInternals.thrownErrors.length) + throw popActScope(prevActQueue, prevActScopeDepth), callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback; + if (null !== result && "object" === typeof result && "function" === typeof result.then) { + var thenable = result; + queueSeveralMicrotasks(function() { + didAwaitActCall || didWarnNoAwaitAct || (didWarnNoAwaitAct = true, console.error( + "You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);" + )); + }); + return { + then: function(resolve, reject) { + didAwaitActCall = true; + thenable.then( + function(returnValue) { + popActScope(prevActQueue, prevActScopeDepth); + if (0 === prevActScopeDepth) { + try { + flushActQueue(queue), enqueueTask(function() { + return recursivelyFlushAsyncActWork( + returnValue, + resolve, + reject + ); + }); + } catch (error$0) { + ReactSharedInternals.thrownErrors.push(error$0); + } + if (0 < ReactSharedInternals.thrownErrors.length) { + var _thrownError = aggregateErrors( + ReactSharedInternals.thrownErrors + ); + ReactSharedInternals.thrownErrors.length = 0; + reject(_thrownError); + } + } else resolve(returnValue); + }, + function(error) { + popActScope(prevActQueue, prevActScopeDepth); + 0 < ReactSharedInternals.thrownErrors.length ? (error = aggregateErrors( + ReactSharedInternals.thrownErrors + ), ReactSharedInternals.thrownErrors.length = 0, reject(error)) : reject(error); + } + ); + } + }; + } + var returnValue$jscomp$0 = result; + popActScope(prevActQueue, prevActScopeDepth); + 0 === prevActScopeDepth && (flushActQueue(queue), 0 !== queue.length && queueSeveralMicrotasks(function() { + didAwaitActCall || didWarnNoAwaitAct || (didWarnNoAwaitAct = true, console.error( + "A component suspended inside an `act` scope, but the `act` call was not awaited. When testing React components that depend on asynchronous data, you must await the result:\n\nawait act(() => ...)" + )); + }), ReactSharedInternals.actQueue = null); + if (0 < ReactSharedInternals.thrownErrors.length) + throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback; + return { + then: function(resolve, reject) { + didAwaitActCall = true; + 0 === prevActScopeDepth ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() { + return recursivelyFlushAsyncActWork( + returnValue$jscomp$0, + resolve, + reject + ); + })) : resolve(returnValue$jscomp$0); + } + }; + }; + exports$1.cache = function(fn) { + return function() { + return fn.apply(null, arguments); + }; + }; + exports$1.cacheSignal = function() { + return null; + }; + exports$1.captureOwnerStack = function() { + var getCurrentStack = ReactSharedInternals.getCurrentStack; + return null === getCurrentStack ? null : getCurrentStack(); + }; + exports$1.cloneElement = function(element, config, children) { + if (null === element || void 0 === element) + throw Error( + "The argument must be a React element, but you passed " + element + "." + ); + var props = assign({}, element.props), key = element.key, owner = element._owner; + if (null != config) { + var JSCompiler_inline_result; + a: { + if (hasOwnProperty.call(config, "ref") && (JSCompiler_inline_result = Object.getOwnPropertyDescriptor( + config, + "ref" + ).get) && JSCompiler_inline_result.isReactWarning) { + JSCompiler_inline_result = false; + break a; + } + JSCompiler_inline_result = void 0 !== config.ref; + } + JSCompiler_inline_result && (owner = getOwner()); + hasValidKey(config) && (checkKeyStringCoercion(config.key), key = "" + config.key); + for (propName in config) + !hasOwnProperty.call(config, propName) || "key" === propName || "__self" === propName || "__source" === propName || "ref" === propName && void 0 === config.ref || (props[propName] = config[propName]); + } + var propName = arguments.length - 2; + if (1 === propName) props.children = children; + else if (1 < propName) { + JSCompiler_inline_result = Array(propName); + for (var i = 0; i < propName; i++) + JSCompiler_inline_result[i] = arguments[i + 2]; + props.children = JSCompiler_inline_result; + } + props = ReactElement( + element.type, + key, + props, + owner, + element._debugStack, + element._debugTask + ); + for (key = 2; key < arguments.length; key++) + validateChildKeys(arguments[key]); + return props; + }; + exports$1.createContext = function(defaultValue) { + defaultValue = { + $$typeof: REACT_CONTEXT_TYPE, + _currentValue: defaultValue, + _currentValue2: defaultValue, + _threadCount: 0, + Provider: null, + Consumer: null + }; + defaultValue.Provider = defaultValue; + defaultValue.Consumer = { + $$typeof: REACT_CONSUMER_TYPE, + _context: defaultValue + }; + defaultValue._currentRenderer = null; + defaultValue._currentRenderer2 = null; + return defaultValue; + }; + exports$1.createElement = function(type, config, children) { + for (var i = 2; i < arguments.length; i++) + validateChildKeys(arguments[i]); + i = {}; + var key = null; + if (null != config) + for (propName in didWarnAboutOldJSXRuntime || !("__self" in config) || "key" in config || (didWarnAboutOldJSXRuntime = true, console.warn( + "Your app (or one of its dependencies) is using an outdated JSX transform. Update to the modern JSX transform for faster performance: https://react.dev/link/new-jsx-transform" + )), hasValidKey(config) && (checkKeyStringCoercion(config.key), key = "" + config.key), config) + hasOwnProperty.call(config, propName) && "key" !== propName && "__self" !== propName && "__source" !== propName && (i[propName] = config[propName]); + var childrenLength = arguments.length - 2; + if (1 === childrenLength) i.children = children; + else if (1 < childrenLength) { + for (var childArray = Array(childrenLength), _i = 0; _i < childrenLength; _i++) + childArray[_i] = arguments[_i + 2]; + Object.freeze && Object.freeze(childArray); + i.children = childArray; + } + if (type && type.defaultProps) + for (propName in childrenLength = type.defaultProps, childrenLength) + void 0 === i[propName] && (i[propName] = childrenLength[propName]); + key && defineKeyPropWarningGetter( + i, + "function" === typeof type ? type.displayName || type.name || "Unknown" : type + ); + var propName = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++; + return ReactElement( + type, + key, + i, + getOwner(), + propName ? Error("react-stack-top-frame") : unknownOwnerDebugStack, + propName ? createTask(getTaskName(type)) : unknownOwnerDebugTask + ); + }; + exports$1.createRef = function() { + var refObject = { current: null }; + Object.seal(refObject); + return refObject; + }; + exports$1.forwardRef = function(render) { + null != render && render.$$typeof === REACT_MEMO_TYPE ? console.error( + "forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))." + ) : "function" !== typeof render ? console.error( + "forwardRef requires a render function but was given %s.", + null === render ? "null" : typeof render + ) : 0 !== render.length && 2 !== render.length && console.error( + "forwardRef render functions accept exactly two parameters: props and ref. %s", + 1 === render.length ? "Did you forget to use the ref parameter?" : "Any additional parameter will be undefined." + ); + null != render && null != render.defaultProps && console.error( + "forwardRef render functions do not support defaultProps. Did you accidentally pass a React component?" + ); + var elementType = { $$typeof: REACT_FORWARD_REF_TYPE, render }, ownName; + Object.defineProperty(elementType, "displayName", { + enumerable: false, + configurable: true, + get: function() { + return ownName; + }, + set: function(name) { + ownName = name; + render.name || render.displayName || (Object.defineProperty(render, "name", { value: name }), render.displayName = name); + } + }); + return elementType; + }; + exports$1.isValidElement = isValidElement; + exports$1.lazy = function(ctor) { + ctor = { _status: -1, _result: ctor }; + var lazyType = { + $$typeof: REACT_LAZY_TYPE, + _payload: ctor, + _init: lazyInitializer + }, ioInfo = { + name: "lazy", + start: -1, + end: -1, + value: null, + owner: null, + debugStack: Error("react-stack-top-frame"), + debugTask: console.createTask ? console.createTask("lazy()") : null + }; + ctor._ioInfo = ioInfo; + lazyType._debugInfo = [{ awaited: ioInfo }]; + return lazyType; + }; + exports$1.memo = function(type, compare) { + null == type && console.error( + "memo: The first argument must be a component. Instead received: %s", + null === type ? "null" : typeof type + ); + compare = { + $$typeof: REACT_MEMO_TYPE, + type, + compare: void 0 === compare ? null : compare + }; + var ownName; + Object.defineProperty(compare, "displayName", { + enumerable: false, + configurable: true, + get: function() { + return ownName; + }, + set: function(name) { + ownName = name; + type.name || type.displayName || (Object.defineProperty(type, "name", { value: name }), type.displayName = name); + } + }); + return compare; + }; + exports$1.startTransition = function(scope) { + var prevTransition = ReactSharedInternals.T, currentTransition = {}; + currentTransition._updatedFibers = /* @__PURE__ */ new Set(); + ReactSharedInternals.T = currentTransition; + try { + var returnValue = scope(), onStartTransitionFinish = ReactSharedInternals.S; + null !== onStartTransitionFinish && onStartTransitionFinish(currentTransition, returnValue); + "object" === typeof returnValue && null !== returnValue && "function" === typeof returnValue.then && (ReactSharedInternals.asyncTransitions++, returnValue.then(releaseAsyncTransition, releaseAsyncTransition), returnValue.then(noop, reportGlobalError)); + } catch (error) { + reportGlobalError(error); + } finally { + null === prevTransition && currentTransition._updatedFibers && (scope = currentTransition._updatedFibers.size, currentTransition._updatedFibers.clear(), 10 < scope && console.warn( + "Detected a large number of updates inside startTransition. If this is due to a subscription please re-write it to use React provided hooks. Otherwise concurrent mode guarantees are off the table." + )), null !== prevTransition && null !== currentTransition.types && (null !== prevTransition.types && prevTransition.types !== currentTransition.types && console.error( + "We expected inner Transitions to have transferred the outer types set and that you cannot add to the outer Transition while inside the inner.This is a bug in React." + ), prevTransition.types = currentTransition.types), ReactSharedInternals.T = prevTransition; + } + }; + exports$1.unstable_useCacheRefresh = function() { + return resolveDispatcher().useCacheRefresh(); + }; + exports$1.use = function(usable) { + return resolveDispatcher().use(usable); + }; + exports$1.useActionState = function(action, initialState, permalink) { + return resolveDispatcher().useActionState( + action, + initialState, + permalink + ); + }; + exports$1.useCallback = function(callback, deps) { + return resolveDispatcher().useCallback(callback, deps); + }; + exports$1.useContext = function(Context) { + var dispatcher = resolveDispatcher(); + Context.$$typeof === REACT_CONSUMER_TYPE && console.error( + "Calling useContext(Context.Consumer) is not supported and will cause bugs. Did you mean to call useContext(Context) instead?" + ); + return dispatcher.useContext(Context); + }; + exports$1.useDebugValue = function(value, formatterFn) { + return resolveDispatcher().useDebugValue(value, formatterFn); + }; + exports$1.useDeferredValue = function(value, initialValue) { + return resolveDispatcher().useDeferredValue(value, initialValue); + }; + exports$1.useEffect = function(create, deps) { + null == create && console.warn( + "React Hook useEffect requires an effect callback. Did you forget to pass a callback to the hook?" + ); + return resolveDispatcher().useEffect(create, deps); + }; + exports$1.useEffectEvent = function(callback) { + return resolveDispatcher().useEffectEvent(callback); + }; + exports$1.useId = function() { + return resolveDispatcher().useId(); + }; + exports$1.useImperativeHandle = function(ref, create, deps) { + return resolveDispatcher().useImperativeHandle(ref, create, deps); + }; + exports$1.useInsertionEffect = function(create, deps) { + null == create && console.warn( + "React Hook useInsertionEffect requires an effect callback. Did you forget to pass a callback to the hook?" + ); + return resolveDispatcher().useInsertionEffect(create, deps); + }; + exports$1.useLayoutEffect = function(create, deps) { + null == create && console.warn( + "React Hook useLayoutEffect requires an effect callback. Did you forget to pass a callback to the hook?" + ); + return resolveDispatcher().useLayoutEffect(create, deps); + }; + exports$1.useMemo = function(create, deps) { + return resolveDispatcher().useMemo(create, deps); + }; + exports$1.useOptimistic = function(passthrough, reducer) { + return resolveDispatcher().useOptimistic(passthrough, reducer); + }; + exports$1.useReducer = function(reducer, initialArg, init) { + return resolveDispatcher().useReducer(reducer, initialArg, init); + }; + exports$1.useRef = function(initialValue) { + return resolveDispatcher().useRef(initialValue); + }; + exports$1.useState = function(initialState) { + return resolveDispatcher().useState(initialState); + }; + exports$1.useSyncExternalStore = function(subscribe, getSnapshot, getServerSnapshot) { + return resolveDispatcher().useSyncExternalStore( + subscribe, + getSnapshot, + getServerSnapshot + ); + }; + exports$1.useTransition = function() { + return resolveDispatcher().useTransition(); + }; + exports$1.version = "19.2.0"; + "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error()); + })(); + } +}); + +// ../../../../../../../node_modules/react/index.js +var require_react = __commonJS({ + "../../../../../../../node_modules/react/index.js"(exports$1, module) { + { + module.exports = require_react_development(); + } + } +}); + +// ../../../../../../../node_modules/react-dom/cjs/react-dom.development.js +var require_react_dom_development = __commonJS({ + "../../../../../../../node_modules/react-dom/cjs/react-dom.development.js"(exports$1) { + (function() { + function noop() { + } + function testStringCoercion(value) { + return "" + value; + } + function createPortal$1(children, containerInfo, implementation) { + var key = 3 < arguments.length && void 0 !== arguments[3] ? arguments[3] : null; + try { + testStringCoercion(key); + var JSCompiler_inline_result = false; + } catch (e) { + JSCompiler_inline_result = true; + } + JSCompiler_inline_result && (console.error( + "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.", + "function" === typeof Symbol && Symbol.toStringTag && key[Symbol.toStringTag] || key.constructor.name || "Object" + ), testStringCoercion(key)); + return { + $$typeof: REACT_PORTAL_TYPE, + key: null == key ? null : "" + key, + children, + containerInfo, + implementation + }; + } + function getCrossOriginStringAs(as, input) { + if ("font" === as) return ""; + if ("string" === typeof input) + return "use-credentials" === input ? input : ""; + } + function getValueDescriptorExpectingObjectForWarning(thing) { + return null === thing ? "`null`" : void 0 === thing ? "`undefined`" : "" === thing ? "an empty string" : 'something with type "' + typeof thing + '"'; + } + function getValueDescriptorExpectingEnumForWarning(thing) { + return null === thing ? "`null`" : void 0 === thing ? "`undefined`" : "" === thing ? "an empty string" : "string" === typeof thing ? JSON.stringify(thing) : "number" === typeof thing ? "`" + thing + "`" : 'something with type "' + typeof thing + '"'; + } + function resolveDispatcher() { + var dispatcher = ReactSharedInternals.H; + null === dispatcher && console.error( + "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem." + ); + return dispatcher; + } + "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error()); + var React2 = require_react(), Internals = { + d: { + f: noop, + r: function() { + throw Error( + "Invalid form element. requestFormReset must be passed a form that was rendered by React." + ); + }, + D: noop, + C: noop, + L: noop, + m: noop, + X: noop, + S: noop, + M: noop + }, + p: 0, + findDOMNode: null + }, REACT_PORTAL_TYPE = Symbol.for("react.portal"), ReactSharedInternals = React2.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; + "function" === typeof Map && null != Map.prototype && "function" === typeof Map.prototype.forEach && "function" === typeof Set && null != Set.prototype && "function" === typeof Set.prototype.clear && "function" === typeof Set.prototype.forEach || console.error( + "React depends on Map and Set built-in types. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills" + ); + exports$1.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = Internals; + exports$1.createPortal = function(children, container) { + var key = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : null; + if (!container || 1 !== container.nodeType && 9 !== container.nodeType && 11 !== container.nodeType) + throw Error("Target container is not a DOM element."); + return createPortal$1(children, container, null, key); + }; + exports$1.flushSync = function(fn) { + var previousTransition = ReactSharedInternals.T, previousUpdatePriority = Internals.p; + try { + if (ReactSharedInternals.T = null, Internals.p = 2, fn) + return fn(); + } finally { + ReactSharedInternals.T = previousTransition, Internals.p = previousUpdatePriority, Internals.d.f() && console.error( + "flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task." + ); + } + }; + exports$1.preconnect = function(href, options) { + "string" === typeof href && href ? null != options && "object" !== typeof options ? console.error( + "ReactDOM.preconnect(): Expected the `options` argument (second) to be an object but encountered %s instead. The only supported option at this time is `crossOrigin` which accepts a string.", + getValueDescriptorExpectingEnumForWarning(options) + ) : null != options && "string" !== typeof options.crossOrigin && console.error( + "ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered %s instead. Try removing this option or passing a string value instead.", + getValueDescriptorExpectingObjectForWarning(options.crossOrigin) + ) : console.error( + "ReactDOM.preconnect(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.", + getValueDescriptorExpectingObjectForWarning(href) + ); + "string" === typeof href && (options ? (options = options.crossOrigin, options = "string" === typeof options ? "use-credentials" === options ? options : "" : void 0) : options = null, Internals.d.C(href, options)); + }; + exports$1.prefetchDNS = function(href) { + if ("string" !== typeof href || !href) + console.error( + "ReactDOM.prefetchDNS(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.", + getValueDescriptorExpectingObjectForWarning(href) + ); + else if (1 < arguments.length) { + var options = arguments[1]; + "object" === typeof options && options.hasOwnProperty("crossOrigin") ? console.error( + "ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.", + getValueDescriptorExpectingEnumForWarning(options) + ) : console.error( + "ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.", + getValueDescriptorExpectingEnumForWarning(options) + ); + } + "string" === typeof href && Internals.d.D(href); + }; + exports$1.preinit = function(href, options) { + "string" === typeof href && href ? null == options || "object" !== typeof options ? console.error( + "ReactDOM.preinit(): Expected the `options` argument (second) to be an object with an `as` property describing the type of resource to be preinitialized but encountered %s instead.", + getValueDescriptorExpectingEnumForWarning(options) + ) : "style" !== options.as && "script" !== options.as && console.error( + 'ReactDOM.preinit(): Expected the `as` property in the `options` argument (second) to contain a valid value describing the type of resource to be preinitialized but encountered %s instead. Valid values for `as` are "style" and "script".', + getValueDescriptorExpectingEnumForWarning(options.as) + ) : console.error( + "ReactDOM.preinit(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.", + getValueDescriptorExpectingObjectForWarning(href) + ); + if ("string" === typeof href && options && "string" === typeof options.as) { + var as = options.as, crossOrigin = getCrossOriginStringAs(as, options.crossOrigin), integrity = "string" === typeof options.integrity ? options.integrity : void 0, fetchPriority = "string" === typeof options.fetchPriority ? options.fetchPriority : void 0; + "style" === as ? Internals.d.S( + href, + "string" === typeof options.precedence ? options.precedence : void 0, + { + crossOrigin, + integrity, + fetchPriority + } + ) : "script" === as && Internals.d.X(href, { + crossOrigin, + integrity, + fetchPriority, + nonce: "string" === typeof options.nonce ? options.nonce : void 0 + }); + } + }; + exports$1.preinitModule = function(href, options) { + var encountered = ""; + "string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + "."); + void 0 !== options && "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : options && "as" in options && "script" !== options.as && (encountered += " The `as` option encountered was " + getValueDescriptorExpectingEnumForWarning(options.as) + "."); + if (encountered) + console.error( + "ReactDOM.preinitModule(): Expected up to two arguments, a non-empty `href` string and, optionally, an `options` object with a valid `as` property.%s", + encountered + ); + else + switch (encountered = options && "string" === typeof options.as ? options.as : "script", encountered) { + case "script": + break; + default: + encountered = getValueDescriptorExpectingEnumForWarning(encountered), console.error( + 'ReactDOM.preinitModule(): Currently the only supported "as" type for this function is "script" but received "%s" instead. This warning was generated for `href` "%s". In the future other module types will be supported, aligning with the import-attributes proposal. Learn more here: (https://github.com/tc39/proposal-import-attributes)', + encountered, + href + ); + } + if ("string" === typeof href) + if ("object" === typeof options && null !== options) { + if (null == options.as || "script" === options.as) + encountered = getCrossOriginStringAs( + options.as, + options.crossOrigin + ), Internals.d.M(href, { + crossOrigin: encountered, + integrity: "string" === typeof options.integrity ? options.integrity : void 0, + nonce: "string" === typeof options.nonce ? options.nonce : void 0 + }); + } else null == options && Internals.d.M(href); + }; + exports$1.preload = function(href, options) { + var encountered = ""; + "string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + "."); + null == options || "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : "string" === typeof options.as && options.as || (encountered += " The `as` option encountered was " + getValueDescriptorExpectingObjectForWarning(options.as) + "."); + encountered && console.error( + 'ReactDOM.preload(): Expected two arguments, a non-empty `href` string and an `options` object with an `as` property valid for a `` tag.%s', + encountered + ); + if ("string" === typeof href && "object" === typeof options && null !== options && "string" === typeof options.as) { + encountered = options.as; + var crossOrigin = getCrossOriginStringAs( + encountered, + options.crossOrigin + ); + Internals.d.L(href, encountered, { + crossOrigin, + integrity: "string" === typeof options.integrity ? options.integrity : void 0, + nonce: "string" === typeof options.nonce ? options.nonce : void 0, + type: "string" === typeof options.type ? options.type : void 0, + fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0, + referrerPolicy: "string" === typeof options.referrerPolicy ? options.referrerPolicy : void 0, + imageSrcSet: "string" === typeof options.imageSrcSet ? options.imageSrcSet : void 0, + imageSizes: "string" === typeof options.imageSizes ? options.imageSizes : void 0, + media: "string" === typeof options.media ? options.media : void 0 + }); + } + }; + exports$1.preloadModule = function(href, options) { + var encountered = ""; + "string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + "."); + void 0 !== options && "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : options && "as" in options && "string" !== typeof options.as && (encountered += " The `as` option encountered was " + getValueDescriptorExpectingObjectForWarning(options.as) + "."); + encountered && console.error( + 'ReactDOM.preloadModule(): Expected two arguments, a non-empty `href` string and, optionally, an `options` object with an `as` property valid for a `` tag.%s', + encountered + ); + "string" === typeof href && (options ? (encountered = getCrossOriginStringAs( + options.as, + options.crossOrigin + ), Internals.d.m(href, { + as: "string" === typeof options.as && "script" !== options.as ? options.as : void 0, + crossOrigin: encountered, + integrity: "string" === typeof options.integrity ? options.integrity : void 0 + })) : Internals.d.m(href)); + }; + exports$1.requestFormReset = function(form) { + Internals.d.r(form); + }; + exports$1.unstable_batchedUpdates = function(fn, a) { + return fn(a); + }; + exports$1.useFormState = function(action, initialState, permalink) { + return resolveDispatcher().useFormState(action, initialState, permalink); + }; + exports$1.useFormStatus = function() { + return resolveDispatcher().useHostTransitionStatus(); + }; + exports$1.version = "19.2.0"; + "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error()); + })(); + } +}); + +// ../../../../../../../node_modules/react-dom/index.js +var require_react_dom = __commonJS({ + "../../../../../../../node_modules/react-dom/index.js"(exports$1, module) { + { + module.exports = require_react_dom_development(); + } + } +}); + +// ../../../../../../../node_modules/react-dom/cjs/react-dom-client.development.js +var require_react_dom_client_development = __commonJS({ + "../../../../../../../node_modules/react-dom/cjs/react-dom-client.development.js"(exports$1) { + (function() { + function findHook(fiber, id) { + for (fiber = fiber.memoizedState; null !== fiber && 0 < id; ) + fiber = fiber.next, id--; + return fiber; + } + function copyWithSetImpl(obj, path, index, value) { + if (index >= path.length) return value; + var key = path[index], updated = isArrayImpl(obj) ? obj.slice() : assign({}, obj); + updated[key] = copyWithSetImpl(obj[key], path, index + 1, value); + return updated; + } + function copyWithRename(obj, oldPath, newPath) { + if (oldPath.length !== newPath.length) + console.warn("copyWithRename() expects paths of the same length"); + else { + for (var i = 0; i < newPath.length - 1; i++) + if (oldPath[i] !== newPath[i]) { + console.warn( + "copyWithRename() expects paths to be the same except for the deepest key" + ); + return; + } + return copyWithRenameImpl(obj, oldPath, newPath, 0); + } + } + function copyWithRenameImpl(obj, oldPath, newPath, index) { + var oldKey = oldPath[index], updated = isArrayImpl(obj) ? obj.slice() : assign({}, obj); + index + 1 === oldPath.length ? (updated[newPath[index]] = updated[oldKey], isArrayImpl(updated) ? updated.splice(oldKey, 1) : delete updated[oldKey]) : updated[oldKey] = copyWithRenameImpl( + obj[oldKey], + oldPath, + newPath, + index + 1 + ); + return updated; + } + function copyWithDeleteImpl(obj, path, index) { + var key = path[index], updated = isArrayImpl(obj) ? obj.slice() : assign({}, obj); + if (index + 1 === path.length) + return isArrayImpl(updated) ? updated.splice(key, 1) : delete updated[key], updated; + updated[key] = copyWithDeleteImpl(obj[key], path, index + 1); + return updated; + } + function shouldSuspendImpl() { + return false; + } + function shouldErrorImpl() { + return null; + } + function warnInvalidHookAccess() { + console.error( + "Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. You can only call Hooks at the top level of your React function. For more information, see https://react.dev/link/rules-of-hooks" + ); + } + function warnInvalidContextAccess() { + console.error( + "Context can only be read while React is rendering. In classes, you can read it in the render method or getDerivedStateFromProps. In function components, you can read it directly in the function body, but not inside Hooks like useReducer() or useMemo()." + ); + } + function noop() { + } + function warnForMissingKey() { + } + function setToSortedString(set) { + var array = []; + set.forEach(function(value) { + array.push(value); + }); + return array.sort().join(", "); + } + function createFiber(tag, pendingProps, key, mode) { + return new FiberNode(tag, pendingProps, key, mode); + } + function scheduleRoot(root2, element) { + root2.context === emptyContextObject && (updateContainerImpl(root2.current, 2, element, root2, null, null), flushSyncWork$1()); + } + function scheduleRefresh(root2, update) { + if (null !== resolveFamily) { + var staleFamilies = update.staleFamilies; + update = update.updatedFamilies; + flushPendingEffects(); + scheduleFibersWithFamiliesRecursively( + root2.current, + update, + staleFamilies + ); + flushSyncWork$1(); + } + } + function setRefreshHandler(handler) { + resolveFamily = handler; + } + function isValidContainer(node) { + return !(!node || 1 !== node.nodeType && 9 !== node.nodeType && 11 !== node.nodeType); + } + function getNearestMountedFiber(fiber) { + var node = fiber, nearestMounted = fiber; + if (fiber.alternate) for (; node.return; ) node = node.return; + else { + fiber = node; + do + node = fiber, 0 !== (node.flags & 4098) && (nearestMounted = node.return), fiber = node.return; + while (fiber); + } + return 3 === node.tag ? nearestMounted : null; + } + function getSuspenseInstanceFromFiber(fiber) { + if (13 === fiber.tag) { + var suspenseState = fiber.memoizedState; + null === suspenseState && (fiber = fiber.alternate, null !== fiber && (suspenseState = fiber.memoizedState)); + if (null !== suspenseState) return suspenseState.dehydrated; + } + return null; + } + function getActivityInstanceFromFiber(fiber) { + if (31 === fiber.tag) { + var activityState = fiber.memoizedState; + null === activityState && (fiber = fiber.alternate, null !== fiber && (activityState = fiber.memoizedState)); + if (null !== activityState) return activityState.dehydrated; + } + return null; + } + function assertIsMounted(fiber) { + if (getNearestMountedFiber(fiber) !== fiber) + throw Error("Unable to find node on an unmounted component."); + } + function findCurrentFiberUsingSlowPath(fiber) { + var alternate = fiber.alternate; + if (!alternate) { + alternate = getNearestMountedFiber(fiber); + if (null === alternate) + throw Error("Unable to find node on an unmounted component."); + return alternate !== fiber ? null : fiber; + } + for (var a = fiber, b = alternate; ; ) { + var parentA = a.return; + if (null === parentA) break; + var parentB = parentA.alternate; + if (null === parentB) { + b = parentA.return; + if (null !== b) { + a = b; + continue; + } + break; + } + if (parentA.child === parentB.child) { + for (parentB = parentA.child; parentB; ) { + if (parentB === a) return assertIsMounted(parentA), fiber; + if (parentB === b) return assertIsMounted(parentA), alternate; + parentB = parentB.sibling; + } + throw Error("Unable to find node on an unmounted component."); + } + if (a.return !== b.return) a = parentA, b = parentB; + else { + for (var didFindChild = false, _child = parentA.child; _child; ) { + if (_child === a) { + didFindChild = true; + a = parentA; + b = parentB; + break; + } + if (_child === b) { + didFindChild = true; + b = parentA; + a = parentB; + break; + } + _child = _child.sibling; + } + if (!didFindChild) { + for (_child = parentB.child; _child; ) { + if (_child === a) { + didFindChild = true; + a = parentB; + b = parentA; + break; + } + if (_child === b) { + didFindChild = true; + b = parentB; + a = parentA; + break; + } + _child = _child.sibling; + } + if (!didFindChild) + throw Error( + "Child was not found in either parent set. This indicates a bug in React related to the return pointer. Please file an issue." + ); + } + } + if (a.alternate !== b) + throw Error( + "Return fibers should always be each others' alternates. This error is likely caused by a bug in React. Please file an issue." + ); + } + if (3 !== a.tag) + throw Error("Unable to find node on an unmounted component."); + return a.stateNode.current === a ? fiber : alternate; + } + function findCurrentHostFiberImpl(node) { + var tag = node.tag; + if (5 === tag || 26 === tag || 27 === tag || 6 === tag) return node; + for (node = node.child; null !== node; ) { + tag = findCurrentHostFiberImpl(node); + if (null !== tag) return tag; + node = node.sibling; + } + return null; + } + function getIteratorFn(maybeIterable) { + if (null === maybeIterable || "object" !== typeof maybeIterable) + return null; + maybeIterable = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable["@@iterator"]; + return "function" === typeof maybeIterable ? maybeIterable : null; + } + function getComponentNameFromType(type) { + if (null == type) return null; + if ("function" === typeof type) + return type.$$typeof === REACT_CLIENT_REFERENCE ? null : type.displayName || type.name || null; + if ("string" === typeof type) return type; + switch (type) { + case REACT_FRAGMENT_TYPE: + return "Fragment"; + case REACT_PROFILER_TYPE: + return "Profiler"; + case REACT_STRICT_MODE_TYPE: + return "StrictMode"; + case REACT_SUSPENSE_TYPE: + return "Suspense"; + case REACT_SUSPENSE_LIST_TYPE: + return "SuspenseList"; + case REACT_ACTIVITY_TYPE: + return "Activity"; + } + if ("object" === typeof type) + switch ("number" === typeof type.tag && console.error( + "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue." + ), type.$$typeof) { + case REACT_PORTAL_TYPE: + return "Portal"; + case REACT_CONTEXT_TYPE: + return type.displayName || "Context"; + case REACT_CONSUMER_TYPE: + return (type._context.displayName || "Context") + ".Consumer"; + case REACT_FORWARD_REF_TYPE: + var innerType = type.render; + type = type.displayName; + type || (type = innerType.displayName || innerType.name || "", type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef"); + return type; + case REACT_MEMO_TYPE: + return innerType = type.displayName || null, null !== innerType ? innerType : getComponentNameFromType(type.type) || "Memo"; + case REACT_LAZY_TYPE: + innerType = type._payload; + type = type._init; + try { + return getComponentNameFromType(type(innerType)); + } catch (x) { + } + } + return null; + } + function getComponentNameFromOwner(owner) { + return "number" === typeof owner.tag ? getComponentNameFromFiber(owner) : "string" === typeof owner.name ? owner.name : null; + } + function getComponentNameFromFiber(fiber) { + var type = fiber.type; + switch (fiber.tag) { + case 31: + return "Activity"; + case 24: + return "Cache"; + case 9: + return (type._context.displayName || "Context") + ".Consumer"; + case 10: + return type.displayName || "Context"; + case 18: + return "DehydratedFragment"; + case 11: + return fiber = type.render, fiber = fiber.displayName || fiber.name || "", type.displayName || ("" !== fiber ? "ForwardRef(" + fiber + ")" : "ForwardRef"); + case 7: + return "Fragment"; + case 26: + case 27: + case 5: + return type; + case 4: + return "Portal"; + case 3: + return "Root"; + case 6: + return "Text"; + case 16: + return getComponentNameFromType(type); + case 8: + return type === REACT_STRICT_MODE_TYPE ? "StrictMode" : "Mode"; + case 22: + return "Offscreen"; + case 12: + return "Profiler"; + case 21: + return "Scope"; + case 13: + return "Suspense"; + case 19: + return "SuspenseList"; + case 25: + return "TracingMarker"; + case 1: + case 0: + case 14: + case 15: + if ("function" === typeof type) + return type.displayName || type.name || null; + if ("string" === typeof type) return type; + break; + case 29: + type = fiber._debugInfo; + if (null != type) { + for (var i = type.length - 1; 0 <= i; i--) + if ("string" === typeof type[i].name) return type[i].name; + } + if (null !== fiber.return) + return getComponentNameFromFiber(fiber.return); + } + return null; + } + function createCursor(defaultValue) { + return { current: defaultValue }; + } + function pop(cursor, fiber) { + 0 > index$jscomp$0 ? console.error("Unexpected pop.") : (fiber !== fiberStack[index$jscomp$0] && console.error("Unexpected Fiber popped."), cursor.current = valueStack[index$jscomp$0], valueStack[index$jscomp$0] = null, fiberStack[index$jscomp$0] = null, index$jscomp$0--); + } + function push(cursor, value, fiber) { + index$jscomp$0++; + valueStack[index$jscomp$0] = cursor.current; + fiberStack[index$jscomp$0] = fiber; + cursor.current = value; + } + function requiredContext(c) { + null === c && console.error( + "Expected host context to exist. This error is likely caused by a bug in React. Please file an issue." + ); + return c; + } + function pushHostContainer(fiber, nextRootInstance) { + push(rootInstanceStackCursor, nextRootInstance, fiber); + push(contextFiberStackCursor, fiber, fiber); + push(contextStackCursor, null, fiber); + var nextRootContext = nextRootInstance.nodeType; + switch (nextRootContext) { + case 9: + case 11: + nextRootContext = 9 === nextRootContext ? "#document" : "#fragment"; + nextRootInstance = (nextRootInstance = nextRootInstance.documentElement) ? (nextRootInstance = nextRootInstance.namespaceURI) ? getOwnHostContext(nextRootInstance) : HostContextNamespaceNone : HostContextNamespaceNone; + break; + default: + if (nextRootContext = nextRootInstance.tagName, nextRootInstance = nextRootInstance.namespaceURI) + nextRootInstance = getOwnHostContext(nextRootInstance), nextRootInstance = getChildHostContextProd( + nextRootInstance, + nextRootContext + ); + else + switch (nextRootContext) { + case "svg": + nextRootInstance = HostContextNamespaceSvg; + break; + case "math": + nextRootInstance = HostContextNamespaceMath; + break; + default: + nextRootInstance = HostContextNamespaceNone; + } + } + nextRootContext = nextRootContext.toLowerCase(); + nextRootContext = updatedAncestorInfoDev(null, nextRootContext); + nextRootContext = { + context: nextRootInstance, + ancestorInfo: nextRootContext + }; + pop(contextStackCursor, fiber); + push(contextStackCursor, nextRootContext, fiber); + } + function popHostContainer(fiber) { + pop(contextStackCursor, fiber); + pop(contextFiberStackCursor, fiber); + pop(rootInstanceStackCursor, fiber); + } + function getHostContext() { + return requiredContext(contextStackCursor.current); + } + function pushHostContext(fiber) { + null !== fiber.memoizedState && push(hostTransitionProviderCursor, fiber, fiber); + var context = requiredContext(contextStackCursor.current); + var type = fiber.type; + var nextContext = getChildHostContextProd(context.context, type); + type = updatedAncestorInfoDev(context.ancestorInfo, type); + nextContext = { context: nextContext, ancestorInfo: type }; + context !== nextContext && (push(contextFiberStackCursor, fiber, fiber), push(contextStackCursor, nextContext, fiber)); + } + function popHostContext(fiber) { + contextFiberStackCursor.current === fiber && (pop(contextStackCursor, fiber), pop(contextFiberStackCursor, fiber)); + hostTransitionProviderCursor.current === fiber && (pop(hostTransitionProviderCursor, fiber), HostTransitionContext._currentValue = NotPendingTransition); + } + function disabledLog() { + } + function disableLogs() { + if (0 === disabledDepth) { + prevLog = console.log; + prevInfo = console.info; + prevWarn = console.warn; + prevError = console.error; + prevGroup = console.group; + prevGroupCollapsed = console.groupCollapsed; + prevGroupEnd = console.groupEnd; + var props = { + configurable: true, + enumerable: true, + value: disabledLog, + writable: true + }; + Object.defineProperties(console, { + info: props, + log: props, + warn: props, + error: props, + group: props, + groupCollapsed: props, + groupEnd: props + }); + } + disabledDepth++; + } + function reenableLogs() { + disabledDepth--; + if (0 === disabledDepth) { + var props = { configurable: true, enumerable: true, writable: true }; + Object.defineProperties(console, { + log: assign({}, props, { value: prevLog }), + info: assign({}, props, { value: prevInfo }), + warn: assign({}, props, { value: prevWarn }), + error: assign({}, props, { value: prevError }), + group: assign({}, props, { value: prevGroup }), + groupCollapsed: assign({}, props, { value: prevGroupCollapsed }), + groupEnd: assign({}, props, { value: prevGroupEnd }) + }); + } + 0 > disabledDepth && console.error( + "disabledDepth fell below zero. This is a bug in React. Please file an issue." + ); + } + function formatOwnerStack(error) { + var prevPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = void 0; + error = error.stack; + Error.prepareStackTrace = prevPrepareStackTrace; + error.startsWith("Error: react-stack-top-frame\n") && (error = error.slice(29)); + prevPrepareStackTrace = error.indexOf("\n"); + -1 !== prevPrepareStackTrace && (error = error.slice(prevPrepareStackTrace + 1)); + prevPrepareStackTrace = error.indexOf("react_stack_bottom_frame"); + -1 !== prevPrepareStackTrace && (prevPrepareStackTrace = error.lastIndexOf( + "\n", + prevPrepareStackTrace + )); + if (-1 !== prevPrepareStackTrace) + error = error.slice(0, prevPrepareStackTrace); + else return ""; + return error; + } + function describeBuiltInComponentFrame(name) { + if (void 0 === prefix) + try { + throw Error(); + } catch (x) { + var match = x.stack.trim().match(/\n( *(at )?)/); + prefix = match && match[1] || ""; + suffix = -1 < x.stack.indexOf("\n at") ? " ()" : -1 < x.stack.indexOf("@") ? "@unknown:0:0" : ""; + } + return "\n" + prefix + name + suffix; + } + function describeNativeComponentFrame(fn, construct) { + if (!fn || reentry) return ""; + var frame = componentFrameCache.get(fn); + if (void 0 !== frame) return frame; + reentry = true; + frame = Error.prepareStackTrace; + Error.prepareStackTrace = void 0; + var previousDispatcher2 = null; + previousDispatcher2 = ReactSharedInternals.H; + ReactSharedInternals.H = null; + disableLogs(); + try { + var RunInRootFrame = { + DetermineComponentFrameRoot: function() { + try { + if (construct) { + var Fake = function() { + throw Error(); + }; + Object.defineProperty(Fake.prototype, "props", { + set: function() { + throw Error(); + } + }); + if ("object" === typeof Reflect && Reflect.construct) { + try { + Reflect.construct(Fake, []); + } catch (x) { + var control = x; + } + Reflect.construct(fn, [], Fake); + } else { + try { + Fake.call(); + } catch (x$0) { + control = x$0; + } + fn.call(Fake.prototype); + } + } else { + try { + throw Error(); + } catch (x$1) { + control = x$1; + } + (Fake = fn()) && "function" === typeof Fake.catch && Fake.catch(function() { + }); + } + } catch (sample) { + if (sample && control && "string" === typeof sample.stack) + return [sample.stack, control.stack]; + } + return [null, null]; + } + }; + RunInRootFrame.DetermineComponentFrameRoot.displayName = "DetermineComponentFrameRoot"; + var namePropDescriptor = Object.getOwnPropertyDescriptor( + RunInRootFrame.DetermineComponentFrameRoot, + "name" + ); + namePropDescriptor && namePropDescriptor.configurable && Object.defineProperty( + RunInRootFrame.DetermineComponentFrameRoot, + "name", + { value: "DetermineComponentFrameRoot" } + ); + var _RunInRootFrame$Deter = RunInRootFrame.DetermineComponentFrameRoot(), sampleStack = _RunInRootFrame$Deter[0], controlStack = _RunInRootFrame$Deter[1]; + if (sampleStack && controlStack) { + var sampleLines = sampleStack.split("\n"), controlLines = controlStack.split("\n"); + for (_RunInRootFrame$Deter = namePropDescriptor = 0; namePropDescriptor < sampleLines.length && !sampleLines[namePropDescriptor].includes( + "DetermineComponentFrameRoot" + ); ) + namePropDescriptor++; + for (; _RunInRootFrame$Deter < controlLines.length && !controlLines[_RunInRootFrame$Deter].includes( + "DetermineComponentFrameRoot" + ); ) + _RunInRootFrame$Deter++; + if (namePropDescriptor === sampleLines.length || _RunInRootFrame$Deter === controlLines.length) + for (namePropDescriptor = sampleLines.length - 1, _RunInRootFrame$Deter = controlLines.length - 1; 1 <= namePropDescriptor && 0 <= _RunInRootFrame$Deter && sampleLines[namePropDescriptor] !== controlLines[_RunInRootFrame$Deter]; ) + _RunInRootFrame$Deter--; + for (; 1 <= namePropDescriptor && 0 <= _RunInRootFrame$Deter; namePropDescriptor--, _RunInRootFrame$Deter--) + if (sampleLines[namePropDescriptor] !== controlLines[_RunInRootFrame$Deter]) { + if (1 !== namePropDescriptor || 1 !== _RunInRootFrame$Deter) { + do + if (namePropDescriptor--, _RunInRootFrame$Deter--, 0 > _RunInRootFrame$Deter || sampleLines[namePropDescriptor] !== controlLines[_RunInRootFrame$Deter]) { + var _frame = "\n" + sampleLines[namePropDescriptor].replace( + " at new ", + " at " + ); + fn.displayName && _frame.includes("") && (_frame = _frame.replace("", fn.displayName)); + "function" === typeof fn && componentFrameCache.set(fn, _frame); + return _frame; + } + while (1 <= namePropDescriptor && 0 <= _RunInRootFrame$Deter); + } + break; + } + } + } finally { + reentry = false, ReactSharedInternals.H = previousDispatcher2, reenableLogs(), Error.prepareStackTrace = frame; + } + sampleLines = (sampleLines = fn ? fn.displayName || fn.name : "") ? describeBuiltInComponentFrame(sampleLines) : ""; + "function" === typeof fn && componentFrameCache.set(fn, sampleLines); + return sampleLines; + } + function describeFiber(fiber, childFiber) { + switch (fiber.tag) { + case 26: + case 27: + case 5: + return describeBuiltInComponentFrame(fiber.type); + case 16: + return describeBuiltInComponentFrame("Lazy"); + case 13: + return fiber.child !== childFiber && null !== childFiber ? describeBuiltInComponentFrame("Suspense Fallback") : describeBuiltInComponentFrame("Suspense"); + case 19: + return describeBuiltInComponentFrame("SuspenseList"); + case 0: + case 15: + return describeNativeComponentFrame(fiber.type, false); + case 11: + return describeNativeComponentFrame(fiber.type.render, false); + case 1: + return describeNativeComponentFrame(fiber.type, true); + case 31: + return describeBuiltInComponentFrame("Activity"); + default: + return ""; + } + } + function getStackByFiberInDevAndProd(workInProgress2) { + try { + var info = "", previous = null; + do { + info += describeFiber(workInProgress2, previous); + var debugInfo = workInProgress2._debugInfo; + if (debugInfo) + for (var i = debugInfo.length - 1; 0 <= i; i--) { + var entry = debugInfo[i]; + if ("string" === typeof entry.name) { + var JSCompiler_temp_const = info; + a: { + var name = entry.name, env = entry.env, location = entry.debugLocation; + if (null != location) { + var childStack = formatOwnerStack(location), idx = childStack.lastIndexOf("\n"), lastLine = -1 === idx ? childStack : childStack.slice(idx + 1); + if (-1 !== lastLine.indexOf(name)) { + var JSCompiler_inline_result = "\n" + lastLine; + break a; + } + } + JSCompiler_inline_result = describeBuiltInComponentFrame( + name + (env ? " [" + env + "]" : "") + ); + } + info = JSCompiler_temp_const + JSCompiler_inline_result; + } + } + previous = workInProgress2; + workInProgress2 = workInProgress2.return; + } while (workInProgress2); + return info; + } catch (x) { + return "\nError generating stack: " + x.message + "\n" + x.stack; + } + } + function describeFunctionComponentFrameWithoutLineNumber(fn) { + return (fn = fn ? fn.displayName || fn.name : "") ? describeBuiltInComponentFrame(fn) : ""; + } + function getCurrentFiberOwnerNameInDevOrNull() { + if (null === current) return null; + var owner = current._debugOwner; + return null != owner ? getComponentNameFromOwner(owner) : null; + } + function getCurrentFiberStackInDev() { + if (null === current) return ""; + var workInProgress2 = current; + try { + var info = ""; + 6 === workInProgress2.tag && (workInProgress2 = workInProgress2.return); + switch (workInProgress2.tag) { + case 26: + case 27: + case 5: + info += describeBuiltInComponentFrame(workInProgress2.type); + break; + case 13: + info += describeBuiltInComponentFrame("Suspense"); + break; + case 19: + info += describeBuiltInComponentFrame("SuspenseList"); + break; + case 31: + info += describeBuiltInComponentFrame("Activity"); + break; + case 30: + case 0: + case 15: + case 1: + workInProgress2._debugOwner || "" !== info || (info += describeFunctionComponentFrameWithoutLineNumber( + workInProgress2.type + )); + break; + case 11: + workInProgress2._debugOwner || "" !== info || (info += describeFunctionComponentFrameWithoutLineNumber( + workInProgress2.type.render + )); + } + for (; workInProgress2; ) + if ("number" === typeof workInProgress2.tag) { + var fiber = workInProgress2; + workInProgress2 = fiber._debugOwner; + var debugStack = fiber._debugStack; + if (workInProgress2 && debugStack) { + var formattedStack = formatOwnerStack(debugStack); + "" !== formattedStack && (info += "\n" + formattedStack); + } + } else if (null != workInProgress2.debugStack) { + var ownerStack = workInProgress2.debugStack; + (workInProgress2 = workInProgress2.owner) && ownerStack && (info += "\n" + formatOwnerStack(ownerStack)); + } else break; + var JSCompiler_inline_result = info; + } catch (x) { + JSCompiler_inline_result = "\nError generating stack: " + x.message + "\n" + x.stack; + } + return JSCompiler_inline_result; + } + function runWithFiberInDEV(fiber, callback, arg0, arg1, arg2, arg3, arg4) { + var previousFiber = current; + setCurrentFiber(fiber); + try { + return null !== fiber && fiber._debugTask ? fiber._debugTask.run( + callback.bind(null, arg0, arg1, arg2, arg3, arg4) + ) : callback(arg0, arg1, arg2, arg3, arg4); + } finally { + setCurrentFiber(previousFiber); + } + throw Error( + "runWithFiberInDEV should never be called in production. This is a bug in React." + ); + } + function setCurrentFiber(fiber) { + ReactSharedInternals.getCurrentStack = null === fiber ? null : getCurrentFiberStackInDev; + isRendering = false; + current = fiber; + } + function typeName(value) { + return "function" === typeof Symbol && Symbol.toStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object"; + } + function willCoercionThrow(value) { + try { + return testStringCoercion(value), false; + } catch (e) { + return true; + } + } + function testStringCoercion(value) { + return "" + value; + } + function checkAttributeStringCoercion(value, attributeName) { + if (willCoercionThrow(value)) + return console.error( + "The provided `%s` attribute is an unsupported type %s. This value must be coerced to a string before using it here.", + attributeName, + typeName(value) + ), testStringCoercion(value); + } + function checkCSSPropertyStringCoercion(value, propName) { + if (willCoercionThrow(value)) + return console.error( + "The provided `%s` CSS property is an unsupported type %s. This value must be coerced to a string before using it here.", + propName, + typeName(value) + ), testStringCoercion(value); + } + function checkFormFieldValueStringCoercion(value) { + if (willCoercionThrow(value)) + return console.error( + "Form field values (value, checked, defaultValue, or defaultChecked props) must be strings, not %s. This value must be coerced to a string before using it here.", + typeName(value) + ), testStringCoercion(value); + } + function injectInternals(internals) { + if ("undefined" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) return false; + var hook = __REACT_DEVTOOLS_GLOBAL_HOOK__; + if (hook.isDisabled) return true; + if (!hook.supportsFiber) + return console.error( + "The installed version of React DevTools is too old and will not work with the current version of React. Please update React DevTools. https://react.dev/link/react-devtools" + ), true; + try { + rendererID = hook.inject(internals), injectedHook = hook; + } catch (err) { + console.error("React instrumentation encountered an error: %o.", err); + } + return hook.checkDCE ? true : false; + } + function setIsStrictModeForDevtools(newIsStrictMode) { + "function" === typeof log$1 && unstable_setDisableYieldValue(newIsStrictMode); + if (injectedHook && "function" === typeof injectedHook.setStrictMode) + try { + injectedHook.setStrictMode(rendererID, newIsStrictMode); + } catch (err) { + hasLoggedError || (hasLoggedError = true, console.error( + "React instrumentation encountered an error: %o", + err + )); + } + } + function clz32Fallback(x) { + x >>>= 0; + return 0 === x ? 32 : 31 - (log(x) / LN2 | 0) | 0; + } + function getHighestPriorityLanes(lanes) { + var pendingSyncLanes = lanes & 42; + if (0 !== pendingSyncLanes) return pendingSyncLanes; + switch (lanes & -lanes) { + case 1: + return 1; + case 2: + return 2; + case 4: + return 4; + case 8: + return 8; + case 16: + return 16; + case 32: + return 32; + case 64: + return 64; + case 128: + return 128; + case 256: + case 512: + case 1024: + case 2048: + case 4096: + case 8192: + case 16384: + case 32768: + case 65536: + case 131072: + return lanes & 261888; + case 262144: + case 524288: + case 1048576: + case 2097152: + return lanes & 3932160; + case 4194304: + case 8388608: + case 16777216: + case 33554432: + return lanes & 62914560; + case 67108864: + return 67108864; + case 134217728: + return 134217728; + case 268435456: + return 268435456; + case 536870912: + return 536870912; + case 1073741824: + return 0; + default: + return console.error( + "Should have found matching lanes. This is a bug in React." + ), lanes; + } + } + function getNextLanes(root2, wipLanes, rootHasPendingCommit) { + var pendingLanes = root2.pendingLanes; + if (0 === pendingLanes) return 0; + var nextLanes = 0, suspendedLanes = root2.suspendedLanes, pingedLanes = root2.pingedLanes; + root2 = root2.warmLanes; + var nonIdlePendingLanes = pendingLanes & 134217727; + 0 !== nonIdlePendingLanes ? (pendingLanes = nonIdlePendingLanes & ~suspendedLanes, 0 !== pendingLanes ? nextLanes = getHighestPriorityLanes(pendingLanes) : (pingedLanes &= nonIdlePendingLanes, 0 !== pingedLanes ? nextLanes = getHighestPriorityLanes(pingedLanes) : rootHasPendingCommit || (rootHasPendingCommit = nonIdlePendingLanes & ~root2, 0 !== rootHasPendingCommit && (nextLanes = getHighestPriorityLanes(rootHasPendingCommit))))) : (nonIdlePendingLanes = pendingLanes & ~suspendedLanes, 0 !== nonIdlePendingLanes ? nextLanes = getHighestPriorityLanes(nonIdlePendingLanes) : 0 !== pingedLanes ? nextLanes = getHighestPriorityLanes(pingedLanes) : rootHasPendingCommit || (rootHasPendingCommit = pendingLanes & ~root2, 0 !== rootHasPendingCommit && (nextLanes = getHighestPriorityLanes(rootHasPendingCommit)))); + return 0 === nextLanes ? 0 : 0 !== wipLanes && wipLanes !== nextLanes && 0 === (wipLanes & suspendedLanes) && (suspendedLanes = nextLanes & -nextLanes, rootHasPendingCommit = wipLanes & -wipLanes, suspendedLanes >= rootHasPendingCommit || 32 === suspendedLanes && 0 !== (rootHasPendingCommit & 4194048)) ? wipLanes : nextLanes; + } + function checkIfRootIsPrerendering(root2, renderLanes2) { + return 0 === (root2.pendingLanes & ~(root2.suspendedLanes & ~root2.pingedLanes) & renderLanes2); + } + function computeExpirationTime(lane, currentTime) { + switch (lane) { + case 1: + case 2: + case 4: + case 8: + case 64: + return currentTime + 250; + case 16: + case 32: + case 128: + case 256: + case 512: + case 1024: + case 2048: + case 4096: + case 8192: + case 16384: + case 32768: + case 65536: + case 131072: + case 262144: + case 524288: + case 1048576: + case 2097152: + return currentTime + 5e3; + case 4194304: + case 8388608: + case 16777216: + case 33554432: + return -1; + case 67108864: + case 134217728: + case 268435456: + case 536870912: + case 1073741824: + return -1; + default: + return console.error( + "Should have found matching lanes. This is a bug in React." + ), -1; + } + } + function claimNextRetryLane() { + var lane = nextRetryLane; + nextRetryLane <<= 1; + 0 === (nextRetryLane & 62914560) && (nextRetryLane = 4194304); + return lane; + } + function createLaneMap(initial) { + for (var laneMap = [], i = 0; 31 > i; i++) laneMap.push(initial); + return laneMap; + } + function markRootUpdated$1(root2, updateLane) { + root2.pendingLanes |= updateLane; + 268435456 !== updateLane && (root2.suspendedLanes = 0, root2.pingedLanes = 0, root2.warmLanes = 0); + } + function markRootFinished(root2, finishedLanes, remainingLanes, spawnedLane, updatedLanes, suspendedRetryLanes) { + var previouslyPendingLanes = root2.pendingLanes; + root2.pendingLanes = remainingLanes; + root2.suspendedLanes = 0; + root2.pingedLanes = 0; + root2.warmLanes = 0; + root2.expiredLanes &= remainingLanes; + root2.entangledLanes &= remainingLanes; + root2.errorRecoveryDisabledLanes &= remainingLanes; + root2.shellSuspendCounter = 0; + var entanglements = root2.entanglements, expirationTimes = root2.expirationTimes, hiddenUpdates = root2.hiddenUpdates; + for (remainingLanes = previouslyPendingLanes & ~remainingLanes; 0 < remainingLanes; ) { + var index = 31 - clz32(remainingLanes), lane = 1 << index; + entanglements[index] = 0; + expirationTimes[index] = -1; + var hiddenUpdatesForLane = hiddenUpdates[index]; + if (null !== hiddenUpdatesForLane) + for (hiddenUpdates[index] = null, index = 0; index < hiddenUpdatesForLane.length; index++) { + var update = hiddenUpdatesForLane[index]; + null !== update && (update.lane &= -536870913); + } + remainingLanes &= ~lane; + } + 0 !== spawnedLane && markSpawnedDeferredLane(root2, spawnedLane, 0); + 0 !== suspendedRetryLanes && 0 === updatedLanes && 0 !== root2.tag && (root2.suspendedLanes |= suspendedRetryLanes & ~(previouslyPendingLanes & ~finishedLanes)); + } + function markSpawnedDeferredLane(root2, spawnedLane, entangledLanes) { + root2.pendingLanes |= spawnedLane; + root2.suspendedLanes &= ~spawnedLane; + var spawnedLaneIndex = 31 - clz32(spawnedLane); + root2.entangledLanes |= spawnedLane; + root2.entanglements[spawnedLaneIndex] = root2.entanglements[spawnedLaneIndex] | 1073741824 | entangledLanes & 261930; + } + function markRootEntangled(root2, entangledLanes) { + var rootEntangledLanes = root2.entangledLanes |= entangledLanes; + for (root2 = root2.entanglements; rootEntangledLanes; ) { + var index = 31 - clz32(rootEntangledLanes), lane = 1 << index; + lane & entangledLanes | root2[index] & entangledLanes && (root2[index] |= entangledLanes); + rootEntangledLanes &= ~lane; + } + } + function getBumpedLaneForHydration(root2, renderLanes2) { + var renderLane = renderLanes2 & -renderLanes2; + renderLane = 0 !== (renderLane & 42) ? 1 : getBumpedLaneForHydrationByLane(renderLane); + return 0 !== (renderLane & (root2.suspendedLanes | renderLanes2)) ? 0 : renderLane; + } + function getBumpedLaneForHydrationByLane(lane) { + switch (lane) { + case 2: + lane = 1; + break; + case 8: + lane = 4; + break; + case 32: + lane = 16; + break; + case 256: + case 512: + case 1024: + case 2048: + case 4096: + case 8192: + case 16384: + case 32768: + case 65536: + case 131072: + case 262144: + case 524288: + case 1048576: + case 2097152: + case 4194304: + case 8388608: + case 16777216: + case 33554432: + lane = 128; + break; + case 268435456: + lane = 134217728; + break; + default: + lane = 0; + } + return lane; + } + function addFiberToLanesMap(root2, fiber, lanes) { + if (isDevToolsPresent) + for (root2 = root2.pendingUpdatersLaneMap; 0 < lanes; ) { + var index = 31 - clz32(lanes), lane = 1 << index; + root2[index].add(fiber); + lanes &= ~lane; + } + } + function movePendingFibersToMemoized(root2, lanes) { + if (isDevToolsPresent) + for (var pendingUpdatersLaneMap = root2.pendingUpdatersLaneMap, memoizedUpdaters = root2.memoizedUpdaters; 0 < lanes; ) { + var index = 31 - clz32(lanes); + root2 = 1 << index; + index = pendingUpdatersLaneMap[index]; + 0 < index.size && (index.forEach(function(fiber) { + var alternate = fiber.alternate; + null !== alternate && memoizedUpdaters.has(alternate) || memoizedUpdaters.add(fiber); + }), index.clear()); + lanes &= ~root2; + } + } + function lanesToEventPriority(lanes) { + lanes &= -lanes; + return DiscreteEventPriority < lanes ? ContinuousEventPriority < lanes ? 0 !== (lanes & 134217727) ? DefaultEventPriority : IdleEventPriority : ContinuousEventPriority : DiscreteEventPriority; + } + function resolveUpdatePriority() { + var updatePriority = ReactDOMSharedInternals.p; + if (0 !== updatePriority) return updatePriority; + updatePriority = window.event; + return void 0 === updatePriority ? DefaultEventPriority : getEventPriority(updatePriority.type); + } + function runWithPriority(priority, fn) { + var previousPriority = ReactDOMSharedInternals.p; + try { + return ReactDOMSharedInternals.p = priority, fn(); + } finally { + ReactDOMSharedInternals.p = previousPriority; + } + } + function detachDeletedInstance(node) { + delete node[internalInstanceKey]; + delete node[internalPropsKey]; + delete node[internalEventHandlersKey]; + delete node[internalEventHandlerListenersKey]; + delete node[internalEventHandlesSetKey]; + } + function getClosestInstanceFromNode(targetNode) { + var targetInst = targetNode[internalInstanceKey]; + if (targetInst) return targetInst; + for (var parentNode = targetNode.parentNode; parentNode; ) { + if (targetInst = parentNode[internalContainerInstanceKey] || parentNode[internalInstanceKey]) { + parentNode = targetInst.alternate; + if (null !== targetInst.child || null !== parentNode && null !== parentNode.child) + for (targetNode = getParentHydrationBoundary(targetNode); null !== targetNode; ) { + if (parentNode = targetNode[internalInstanceKey]) + return parentNode; + targetNode = getParentHydrationBoundary(targetNode); + } + return targetInst; + } + targetNode = parentNode; + parentNode = targetNode.parentNode; + } + return null; + } + function getInstanceFromNode(node) { + if (node = node[internalInstanceKey] || node[internalContainerInstanceKey]) { + var tag = node.tag; + if (5 === tag || 6 === tag || 13 === tag || 31 === tag || 26 === tag || 27 === tag || 3 === tag) + return node; + } + return null; + } + function getNodeFromInstance(inst) { + var tag = inst.tag; + if (5 === tag || 26 === tag || 27 === tag || 6 === tag) + return inst.stateNode; + throw Error("getNodeFromInstance: Invalid argument."); + } + function getResourcesFromRoot(root2) { + var resources = root2[internalRootNodeResourcesKey]; + resources || (resources = root2[internalRootNodeResourcesKey] = { hoistableStyles: /* @__PURE__ */ new Map(), hoistableScripts: /* @__PURE__ */ new Map() }); + return resources; + } + function markNodeAsHoistable(node) { + node[internalHoistableMarker] = true; + } + function registerTwoPhaseEvent(registrationName, dependencies) { + registerDirectEvent(registrationName, dependencies); + registerDirectEvent(registrationName + "Capture", dependencies); + } + function registerDirectEvent(registrationName, dependencies) { + registrationNameDependencies[registrationName] && console.error( + "EventRegistry: More than one plugin attempted to publish the same registration name, `%s`.", + registrationName + ); + registrationNameDependencies[registrationName] = dependencies; + var lowerCasedName = registrationName.toLowerCase(); + possibleRegistrationNames[lowerCasedName] = registrationName; + "onDoubleClick" === registrationName && (possibleRegistrationNames.ondblclick = registrationName); + for (registrationName = 0; registrationName < dependencies.length; registrationName++) + allNativeEvents.add(dependencies[registrationName]); + } + function checkControlledValueProps(tagName, props) { + hasReadOnlyValue[props.type] || props.onChange || props.onInput || props.readOnly || props.disabled || null == props.value || ("select" === tagName ? console.error( + "You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set `onChange`." + ) : console.error( + "You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`." + )); + props.onChange || props.readOnly || props.disabled || null == props.checked || console.error( + "You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`." + ); + } + function isAttributeNameSafe(attributeName) { + if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) + return true; + if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) + return false; + if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) + return validatedAttributeNameCache[attributeName] = true; + illegalAttributeNameCache[attributeName] = true; + console.error("Invalid attribute name: `%s`", attributeName); + return false; + } + function getValueForAttributeOnCustomComponent(node, name, expected) { + if (isAttributeNameSafe(name)) { + if (!node.hasAttribute(name)) { + switch (typeof expected) { + case "symbol": + case "object": + return expected; + case "function": + return expected; + case "boolean": + if (false === expected) return expected; + } + return void 0 === expected ? void 0 : null; + } + node = node.getAttribute(name); + if ("" === node && true === expected) return true; + checkAttributeStringCoercion(expected, name); + return node === "" + expected ? expected : node; + } + } + function setValueForAttribute(node, name, value) { + if (isAttributeNameSafe(name)) + if (null === value) node.removeAttribute(name); + else { + switch (typeof value) { + case "undefined": + case "function": + case "symbol": + node.removeAttribute(name); + return; + case "boolean": + var prefix2 = name.toLowerCase().slice(0, 5); + if ("data-" !== prefix2 && "aria-" !== prefix2) { + node.removeAttribute(name); + return; + } + } + checkAttributeStringCoercion(value, name); + node.setAttribute(name, "" + value); + } + } + function setValueForKnownAttribute(node, name, value) { + if (null === value) node.removeAttribute(name); + else { + switch (typeof value) { + case "undefined": + case "function": + case "symbol": + case "boolean": + node.removeAttribute(name); + return; + } + checkAttributeStringCoercion(value, name); + node.setAttribute(name, "" + value); + } + } + function setValueForNamespacedAttribute(node, namespace, name, value) { + if (null === value) node.removeAttribute(name); + else { + switch (typeof value) { + case "undefined": + case "function": + case "symbol": + case "boolean": + node.removeAttribute(name); + return; + } + checkAttributeStringCoercion(value, name); + node.setAttributeNS(namespace, name, "" + value); + } + } + function getToStringValue(value) { + switch (typeof value) { + case "bigint": + case "boolean": + case "number": + case "string": + case "undefined": + return value; + case "object": + return checkFormFieldValueStringCoercion(value), value; + default: + return ""; + } + } + function isCheckable(elem) { + var type = elem.type; + return (elem = elem.nodeName) && "input" === elem.toLowerCase() && ("checkbox" === type || "radio" === type); + } + function trackValueOnNode(node, valueField, currentValue) { + var descriptor = Object.getOwnPropertyDescriptor( + node.constructor.prototype, + valueField + ); + if (!node.hasOwnProperty(valueField) && "undefined" !== typeof descriptor && "function" === typeof descriptor.get && "function" === typeof descriptor.set) { + var get = descriptor.get, set = descriptor.set; + Object.defineProperty(node, valueField, { + configurable: true, + get: function() { + return get.call(this); + }, + set: function(value) { + checkFormFieldValueStringCoercion(value); + currentValue = "" + value; + set.call(this, value); + } + }); + Object.defineProperty(node, valueField, { + enumerable: descriptor.enumerable + }); + return { + getValue: function() { + return currentValue; + }, + setValue: function(value) { + checkFormFieldValueStringCoercion(value); + currentValue = "" + value; + }, + stopTracking: function() { + node._valueTracker = null; + delete node[valueField]; + } + }; + } + } + function track(node) { + if (!node._valueTracker) { + var valueField = isCheckable(node) ? "checked" : "value"; + node._valueTracker = trackValueOnNode( + node, + valueField, + "" + node[valueField] + ); + } + } + function updateValueIfChanged(node) { + if (!node) return false; + var tracker = node._valueTracker; + if (!tracker) return true; + var lastValue = tracker.getValue(); + var value = ""; + node && (value = isCheckable(node) ? node.checked ? "true" : "false" : node.value); + node = value; + return node !== lastValue ? (tracker.setValue(node), true) : false; + } + function getActiveElement(doc) { + doc = doc || ("undefined" !== typeof document ? document : void 0); + if ("undefined" === typeof doc) return null; + try { + return doc.activeElement || doc.body; + } catch (e) { + return doc.body; + } + } + function escapeSelectorAttributeValueInsideDoubleQuotes(value) { + return value.replace( + escapeSelectorAttributeValueInsideDoubleQuotesRegex, + function(ch) { + return "\\" + ch.charCodeAt(0).toString(16) + " "; + } + ); + } + function validateInputProps(element, props) { + void 0 === props.checked || void 0 === props.defaultChecked || didWarnCheckedDefaultChecked || (console.error( + "%s contains an input of type %s with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://react.dev/link/controlled-components", + getCurrentFiberOwnerNameInDevOrNull() || "A component", + props.type + ), didWarnCheckedDefaultChecked = true); + void 0 === props.value || void 0 === props.defaultValue || didWarnValueDefaultValue$1 || (console.error( + "%s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://react.dev/link/controlled-components", + getCurrentFiberOwnerNameInDevOrNull() || "A component", + props.type + ), didWarnValueDefaultValue$1 = true); + } + function updateInput(element, value, defaultValue, lastDefaultValue, checked, defaultChecked, type, name) { + element.name = ""; + null != type && "function" !== typeof type && "symbol" !== typeof type && "boolean" !== typeof type ? (checkAttributeStringCoercion(type, "type"), element.type = type) : element.removeAttribute("type"); + if (null != value) + if ("number" === type) { + if (0 === value && "" === element.value || element.value != value) + element.value = "" + getToStringValue(value); + } else + element.value !== "" + getToStringValue(value) && (element.value = "" + getToStringValue(value)); + else + "submit" !== type && "reset" !== type || element.removeAttribute("value"); + null != value ? setDefaultValue(element, type, getToStringValue(value)) : null != defaultValue ? setDefaultValue(element, type, getToStringValue(defaultValue)) : null != lastDefaultValue && element.removeAttribute("value"); + null == checked && null != defaultChecked && (element.defaultChecked = !!defaultChecked); + null != checked && (element.checked = checked && "function" !== typeof checked && "symbol" !== typeof checked); + null != name && "function" !== typeof name && "symbol" !== typeof name && "boolean" !== typeof name ? (checkAttributeStringCoercion(name, "name"), element.name = "" + getToStringValue(name)) : element.removeAttribute("name"); + } + function initInput(element, value, defaultValue, checked, defaultChecked, type, name, isHydrating2) { + null != type && "function" !== typeof type && "symbol" !== typeof type && "boolean" !== typeof type && (checkAttributeStringCoercion(type, "type"), element.type = type); + if (null != value || null != defaultValue) { + if (!("submit" !== type && "reset" !== type || void 0 !== value && null !== value)) { + track(element); + return; + } + defaultValue = null != defaultValue ? "" + getToStringValue(defaultValue) : ""; + value = null != value ? "" + getToStringValue(value) : defaultValue; + isHydrating2 || value === element.value || (element.value = value); + element.defaultValue = value; + } + checked = null != checked ? checked : defaultChecked; + checked = "function" !== typeof checked && "symbol" !== typeof checked && !!checked; + element.checked = isHydrating2 ? element.checked : !!checked; + element.defaultChecked = !!checked; + null != name && "function" !== typeof name && "symbol" !== typeof name && "boolean" !== typeof name && (checkAttributeStringCoercion(name, "name"), element.name = name); + track(element); + } + function setDefaultValue(node, type, value) { + "number" === type && getActiveElement(node.ownerDocument) === node || node.defaultValue === "" + value || (node.defaultValue = "" + value); + } + function validateOptionProps(element, props) { + null == props.value && ("object" === typeof props.children && null !== props.children ? React2.Children.forEach(props.children, function(child) { + null == child || "string" === typeof child || "number" === typeof child || "bigint" === typeof child || didWarnInvalidChild || (didWarnInvalidChild = true, console.error( + "Cannot infer the option value of complex children. Pass a `value` prop or use a plain string as children to