mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
Merge branch 'main' into dev/dmitriv/micro-perf
This commit is contained in:
@@ -34,9 +34,6 @@ jobs:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ../common/bump-insiders-version.yml@self
|
||||
|
||||
- template: ../cli/cli-apply-patches.yml@self
|
||||
|
||||
- script: |
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
- name: VSCODE_ARCH
|
||||
type: string
|
||||
|
||||
@@ -57,9 +55,6 @@ jobs:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ../common/bump-insiders-version.yml@self
|
||||
|
||||
- template: ../distro/download-distro.yml@self
|
||||
|
||||
- task: AzureKeyVault@2
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
steps:
|
||||
- script: |
|
||||
set -e
|
||||
BUILD_NAME="$(Build.BuildNumber)" # example "20251114.34 (insider)"
|
||||
VSCODE_PATCH_VERSION="$(echo $BUILD_NAME | cut -d' ' -f1 | awk -F. '{printf "%s%03d", $1, $2}')"
|
||||
VSCODE_MAJOR_MINOR_VERSION="$(node -p "require('./package.json').version.replace(/\.\d+$/, '')")"
|
||||
VSCODE_INSIDERS_VERSION="${VSCODE_MAJOR_MINOR_VERSION}.${VSCODE_PATCH_VERSION}"
|
||||
echo "Setting Insiders version to: $VSCODE_INSIDERS_VERSION"
|
||||
node -e "require('fs').writeFileSync('package.json', JSON.stringify({...require('./package.json'), version: process.argv[1]}, null, 2))" $VSCODE_INSIDERS_VERSION
|
||||
displayName: Override Insiders Version
|
||||
condition: and(succeeded(), not(contains(variables['Agent.OS'], 'windows')))
|
||||
|
||||
- pwsh: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$buildName = "$(Build.BuildNumber)" # example "20251114.34 (insider)"
|
||||
$buildParts = ($buildName -split ' ')[0] -split '\.'
|
||||
$patchVersion = "{0}{1:000}" -f $buildParts[0], [int]$buildParts[1]
|
||||
$majorMinorVersion = node -p "require('./package.json').version.replace(/\.\d+$/, '')"
|
||||
$insidersVersion = "$majorMinorVersion.$patchVersion"
|
||||
Write-Host "Setting Insiders version to: $insidersVersion"
|
||||
node -e "require('fs').writeFileSync('package.json', JSON.stringify({...require('./package.json'), version: process.argv[1]}, null, 2))" $insidersVersion
|
||||
displayName: Override Insiders Version
|
||||
condition: and(succeeded(), contains(variables['Agent.OS'], 'windows'))
|
||||
@@ -1,6 +1,4 @@
|
||||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
- name: VSCODE_CIBUILD
|
||||
type: boolean
|
||||
- name: VSCODE_TEST_SUITE
|
||||
@@ -38,7 +36,6 @@ jobs:
|
||||
steps:
|
||||
- template: ./steps/product-build-darwin-compile.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }}
|
||||
VSCODE_ARCH: arm64
|
||||
VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }}
|
||||
${{ if eq(parameters.VSCODE_TEST_SUITE, 'Electron') }}:
|
||||
|
||||
@@ -35,9 +35,6 @@ jobs:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ../common/bump-insiders-version.yml@self
|
||||
|
||||
- template: ../cli/cli-apply-patches.yml@self
|
||||
|
||||
- task: Npm@1
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
- job: macOSUniversal
|
||||
displayName: macOS (UNIVERSAL)
|
||||
@@ -26,9 +22,6 @@ jobs:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ../common/bump-insiders-version.yml@self
|
||||
|
||||
- template: ../distro/download-distro.yml@self
|
||||
|
||||
- task: AzureKeyVault@2
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
- name: VSCODE_ARCH
|
||||
type: string
|
||||
- name: VSCODE_CIBUILD
|
||||
@@ -74,7 +72,6 @@ jobs:
|
||||
steps:
|
||||
- template: ./steps/product-build-darwin-compile.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }}
|
||||
VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }}
|
||||
VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }}
|
||||
VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
- name: VSCODE_ARCH
|
||||
type: string
|
||||
- name: VSCODE_CIBUILD
|
||||
@@ -23,9 +21,6 @@ steps:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ../../common/bump-insiders-version.yml@self
|
||||
|
||||
- template: ../../distro/download-distro.yml@self
|
||||
|
||||
- task: AzureKeyVault@2
|
||||
|
||||
@@ -34,9 +34,6 @@ jobs:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ../common/bump-insiders-version.yml@self
|
||||
|
||||
- template: ../cli/cli-apply-patches.yml@self
|
||||
|
||||
- task: Npm@1
|
||||
|
||||
@@ -26,9 +26,6 @@ steps:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ../../common/bump-insiders-version.yml@self
|
||||
|
||||
- template: ../../distro/download-distro.yml@self
|
||||
|
||||
- task: AzureKeyVault@2
|
||||
|
||||
@@ -192,8 +192,6 @@ extends:
|
||||
- stage: Compile
|
||||
jobs:
|
||||
- template: build/azure-pipelines/product-compile.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }}
|
||||
|
||||
- ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}:
|
||||
- stage: CompileCLI
|
||||
@@ -411,12 +409,10 @@ extends:
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}:
|
||||
- template: build/azure-pipelines/alpine/product-build-alpine.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_ARCH: x64
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}:
|
||||
- template: build/azure-pipelines/alpine/product-build-alpine.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_ARCH: arm64
|
||||
|
||||
- ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}:
|
||||
@@ -434,31 +430,26 @@ extends:
|
||||
- ${{ if eq(variables['VSCODE_CIBUILD'], true) }}:
|
||||
- template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }}
|
||||
VSCODE_TEST_SUITE: Electron
|
||||
- template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }}
|
||||
VSCODE_TEST_SUITE: Browser
|
||||
- template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }}
|
||||
VSCODE_TEST_SUITE: Remote
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS, true)) }}:
|
||||
- template: build/azure-pipelines/darwin/product-build-darwin.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_ARCH: x64
|
||||
VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }}
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}:
|
||||
- template: build/azure-pipelines/darwin/product-build-darwin.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_ARCH: arm64
|
||||
VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }}
|
||||
VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }}
|
||||
@@ -467,8 +458,6 @@ extends:
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_MACOS_UNIVERSAL'], true)) }}:
|
||||
- template: build/azure-pipelines/darwin/product-build-darwin-universal.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
|
||||
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_MACOS, true), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true))) }}:
|
||||
- template: build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml@self
|
||||
@@ -482,8 +471,6 @@ extends:
|
||||
- Compile
|
||||
jobs:
|
||||
- template: build/azure-pipelines/web/product-build-web.yml@self
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
|
||||
- ${{ if eq(variables['VSCODE_PUBLISH'], 'true') }}:
|
||||
- stage: Publish
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
- job: Compile
|
||||
timeoutInMinutes: 60
|
||||
@@ -24,9 +20,6 @@ jobs:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ./common/bump-insiders-version.yml@self
|
||||
|
||||
- template: ./distro/download-distro.yml@self
|
||||
|
||||
- task: AzureKeyVault@2
|
||||
|
||||
@@ -31,9 +31,6 @@ jobs:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ./common/bump-insiders-version.yml@self
|
||||
|
||||
- task: AzureKeyVault@2
|
||||
displayName: "Azure Key Vault: Get Secrets"
|
||||
inputs:
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
- job: Web
|
||||
displayName: Web
|
||||
@@ -28,9 +24,6 @@ jobs:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ../common/bump-insiders-version.yml@self
|
||||
|
||||
- template: ../distro/download-distro.yml@self
|
||||
|
||||
- task: AzureKeyVault@2
|
||||
|
||||
@@ -34,9 +34,6 @@ jobs:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ../common/bump-insiders-version.yml@self
|
||||
|
||||
- template: ../cli/cli-apply-patches.yml@self
|
||||
|
||||
- task: Npm@1
|
||||
|
||||
@@ -23,9 +23,6 @@ steps:
|
||||
versionSource: fromFile
|
||||
versionFilePath: .nvmrc
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}:
|
||||
- template: ../../common/bump-insiders-version.yml@self
|
||||
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: "3.x"
|
||||
|
||||
@@ -417,9 +417,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
|
||||
if (quality === 'stable' || quality === 'insider') {
|
||||
result = es.merge(result, gulp.src('.build/win32/appx/**', { base: '.build/win32' }));
|
||||
const rawVersion = version.replace(/-\w+$/, '').split('.');
|
||||
|
||||
// AppX doesn't support versions like `1.0.107.20251114039`, so we bring it back down to zero
|
||||
const appxVersion = `${rawVersion[0]}.0.${rawVersion[1]}.${quality === 'insider' ? '0' : rawVersion[2]}`;
|
||||
const appxVersion = `${rawVersion[0]}.0.${rawVersion[1]}.${rawVersion[2]}`;
|
||||
result = es.merge(result, gulp.src('resources/win32/appx/AppxManifest.xml', { base: '.' })
|
||||
.pipe(replace('@@AppxPackageName@@', product.win32AppUserModelId))
|
||||
.pipe(replace('@@AppxPackageVersion@@', appxVersion))
|
||||
|
||||
@@ -83,19 +83,12 @@ function buildWin32Setup(arch, target) {
|
||||
fs.writeFileSync(productJsonPath, JSON.stringify(productJson, undefined, '\t'));
|
||||
|
||||
const quality = product.quality || 'dev';
|
||||
let RawVersion = pkg.version.replace(/-\w+$/, '');
|
||||
|
||||
// InnoSetup doesn't support versions like `1.0.107.20251114039`, so we bring it back down to zero
|
||||
if (quality === 'insider') {
|
||||
RawVersion = RawVersion.replace(/(\d+)$/, '0');
|
||||
}
|
||||
|
||||
const definitions = {
|
||||
NameLong: product.nameLong,
|
||||
NameShort: product.nameShort,
|
||||
DirName: product.win32DirName,
|
||||
Version: pkg.version,
|
||||
RawVersion,
|
||||
RawVersion: pkg.version.replace(/-\w+$/, ''),
|
||||
NameVersion: product.win32NameVersion + (target === 'user' ? ' (User)' : ''),
|
||||
ExeBasename: product.nameShort,
|
||||
RegValueName: product.win32RegValueName,
|
||||
|
||||
@@ -274,7 +274,6 @@ export default tseslint.config(
|
||||
'src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts',
|
||||
'src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts',
|
||||
'src/vs/workbench/contrib/chat/browser/chatSessions/common.ts',
|
||||
'src/vs/workbench/contrib/chat/browser/chatSessions/localChatSessionsProvider.ts',
|
||||
'src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts',
|
||||
'src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts',
|
||||
'src/vs/workbench/contrib/chat/common/annotations.ts',
|
||||
|
||||
Generated
+36
-36
@@ -80,7 +80,7 @@
|
||||
"@types/yauzl": "^2.10.0",
|
||||
"@types/yazl": "^2.4.2",
|
||||
"@typescript-eslint/utils": "^8.45.0",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20251117",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20250812.1",
|
||||
"@vscode/gulp-electron": "^1.38.2",
|
||||
"@vscode/l10n-dev": "0.0.35",
|
||||
"@vscode/telemetry-extractor": "^1.10.2",
|
||||
@@ -152,7 +152,7 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"tsec": "0.2.7",
|
||||
"tslib": "^2.6.3",
|
||||
"typescript": "^6.0.0-dev.20251117",
|
||||
"typescript": "^6.0.0-dev.20251110",
|
||||
"typescript-eslint": "^8.45.0",
|
||||
"util": "^0.12.4",
|
||||
"webpack": "^5.94.0",
|
||||
@@ -2509,28 +2509,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/native-preview": {
|
||||
"version": "7.0.0-dev.20251117.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20251117.1.tgz",
|
||||
"integrity": "sha512-JgKY4Q6jRCszCJ46c8tVrGVnmdiRPSKTW0UQvcyxdI7LG9NYMchJ/W7iUyFZVjG8BV1iUTl3DYml1xErPHLKeg==",
|
||||
"version": "7.0.0-dev.20251110.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20251110.1.tgz",
|
||||
"integrity": "sha512-yzCDN6wUV1kibefOTwxw1MdeIgaJOgN5/a06cMyUlEDcXBriV4O2v+yeXY8c3yzUaVVVO8CKtHPbCMwro4j1Dw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsgo": "bin/tsgo.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251117.1",
|
||||
"@typescript/native-preview-darwin-x64": "7.0.0-dev.20251117.1",
|
||||
"@typescript/native-preview-linux-arm": "7.0.0-dev.20251117.1",
|
||||
"@typescript/native-preview-linux-arm64": "7.0.0-dev.20251117.1",
|
||||
"@typescript/native-preview-linux-x64": "7.0.0-dev.20251117.1",
|
||||
"@typescript/native-preview-win32-arm64": "7.0.0-dev.20251117.1",
|
||||
"@typescript/native-preview-win32-x64": "7.0.0-dev.20251117.1"
|
||||
"@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251110.1",
|
||||
"@typescript/native-preview-darwin-x64": "7.0.0-dev.20251110.1",
|
||||
"@typescript/native-preview-linux-arm": "7.0.0-dev.20251110.1",
|
||||
"@typescript/native-preview-linux-arm64": "7.0.0-dev.20251110.1",
|
||||
"@typescript/native-preview-linux-x64": "7.0.0-dev.20251110.1",
|
||||
"@typescript/native-preview-win32-arm64": "7.0.0-dev.20251110.1",
|
||||
"@typescript/native-preview-win32-x64": "7.0.0-dev.20251110.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/native-preview-darwin-arm64": {
|
||||
"version": "7.0.0-dev.20251117.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20251117.1.tgz",
|
||||
"integrity": "sha512-O7Hhb9m8AZJCAUSBbGmZs7Vm890Kh5Z3xAAASs+L4thtPM0oRckeaoXLvHeE9Qy1p8qG//EmZ3+uSdtUTV4wqg==",
|
||||
"version": "7.0.0-dev.20251110.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20251110.1.tgz",
|
||||
"integrity": "sha512-x3DskzZCgk5qA7BCcCC/8XuZiycvZk5reeqkNTuDYeWyF1ZCKa8WWZRbW5LaunaOtXV6UsAPRCqRC8Wx34mMCg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2542,9 +2542,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@typescript/native-preview-darwin-x64": {
|
||||
"version": "7.0.0-dev.20251117.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20251117.1.tgz",
|
||||
"integrity": "sha512-/I/iWWvUvuy8BK0bXn5Kz6z2QwknwD2kl2estQxgsz9VgHHyLSyjAg7c18pX/re0Z9ISPz7wutEKabzdtRW8Uw==",
|
||||
"version": "7.0.0-dev.20251110.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20251110.1.tgz",
|
||||
"integrity": "sha512-tuS4akGtsPs+RTiVXEXOT41+as23DXCOhzeOEtYYVdhWVuMBYLHksdTx5PGoQrCc4SfETp5jDwhyqUaVYLDGcA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2556,9 +2556,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@typescript/native-preview-linux-arm": {
|
||||
"version": "7.0.0-dev.20251117.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20251117.1.tgz",
|
||||
"integrity": "sha512-Mfnc8CytGICsYJCMbu3FwE/KDcVg4/QTFix6O31oUkj9ERp3zbSePVMQulkJTH2vuhDvJnVISHzIYawtq5QPTQ==",
|
||||
"version": "7.0.0-dev.20251110.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20251110.1.tgz",
|
||||
"integrity": "sha512-I9zOzHXFqIQIcTcf2Sx9EF6gLOKXUCMo5gsjoQm4/R22+19+TMLeAs7Q1aTvd8CX8kFCtpI1eeyNzIf76rxELA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2570,9 +2570,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@typescript/native-preview-linux-arm64": {
|
||||
"version": "7.0.0-dev.20251117.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20251117.1.tgz",
|
||||
"integrity": "sha512-YSkmJb4/WrS6ZMEJSDbv5o2Garms3+3yKsH+Y3JLUab0namf1Br7T53ydW7ijV2rE7j9DgJs9P+GNu8753St3Q==",
|
||||
"version": "7.0.0-dev.20251110.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20251110.1.tgz",
|
||||
"integrity": "sha512-IvSeQ1iw4uvBZ8+XrO9z80J9KfbkbTzfXliPHUsjZqEtpOJTf/Mv7xzMbv4mN4xOEGVUyBG47p846oW2HknogA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2584,9 +2584,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@typescript/native-preview-linux-x64": {
|
||||
"version": "7.0.0-dev.20251117.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20251117.1.tgz",
|
||||
"integrity": "sha512-R5KvnKuGsbozjHbmA+zPa4xVkQSutvtU9/PQJ7vjJL0xsvSsRUgOE2V2jlT+KnfjAhYVoIg2njtHdf0uv5k9Ow==",
|
||||
"version": "7.0.0-dev.20251110.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20251110.1.tgz",
|
||||
"integrity": "sha512-OWy32tgpP70rSRvmQZ6OgJpuv1pi4mQdng00eF3tfHheHluX3mvqqe86H0FOv5B9PuxlGwOZSUot1XHWadhAWg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2598,9 +2598,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@typescript/native-preview-win32-arm64": {
|
||||
"version": "7.0.0-dev.20251117.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20251117.1.tgz",
|
||||
"integrity": "sha512-xfEwDD9BwCm2gFf0AePfvXxjgQ/EDBDLRbSejtShTSFwrgdnRJ7iW63/ns/i31qLesTzGZaLxeAV8zgh6C2Ibg==",
|
||||
"version": "7.0.0-dev.20251110.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20251110.1.tgz",
|
||||
"integrity": "sha512-u/Bo0gIcQCv/4MDnV5f2FZR1dEdN2jk3MfkmJLKGG1zwbak4MY7sWNzvSRJHihwK2SxtcJEHus4tKb2ra2Rhig==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2612,9 +2612,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@typescript/native-preview-win32-x64": {
|
||||
"version": "7.0.0-dev.20251117.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20251117.1.tgz",
|
||||
"integrity": "sha512-GhJ4GIygHSU86gZw6NkOnJKi/XW0Yw+1quanZ6BaOAZ+HY6aftuESy+NlbC6nUSGE2xmbvxqJgqchCIlC6YPoA==",
|
||||
"version": "7.0.0-dev.20251110.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20251110.1.tgz",
|
||||
"integrity": "sha512-1CysgwFRuNjR0bBYv6RI3fbXtAwzD5OlbxqOQFhf2lUulMZRIkP1w4eCChSndLVCTfnUEt5Bnmn1JEUauIE+kQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -17308,9 +17308,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "6.0.0-dev.20251117",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.0-dev.20251117.tgz",
|
||||
"integrity": "sha512-BJkVdQDGWE8KxtuSLvWLQ/ju+n6FdSM8rq/2B9myrmKXeKa9HRG36MOTMgfZQUWDmPd2f5+U8fhU7xO2+WNa3g==",
|
||||
"version": "6.0.0-dev.20251110",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.0-dev.20251110.tgz",
|
||||
"integrity": "sha512-tHG+EJXTSaUCMbTNApOuVE3WmgOmEqUwQiAXnmwsF/sVKhPFHQA0+S1hml0Ro8kpayvD0d9AX5iC2S2s+TIQxQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.107.20251119",
|
||||
"distro": "3ee33b7862b5e018538b730ae631f35747f57a2c",
|
||||
"distro": "70b452e51d85528e38164c11d783791ead374f43",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -69,7 +69,7 @@
|
||||
"extensions-ci": "node ./node_modules/gulp/bin/gulp.js extensions-ci",
|
||||
"extensions-ci-pr": "node ./node_modules/gulp/bin/gulp.js extensions-ci-pr",
|
||||
"perf": "node scripts/code-perf.js",
|
||||
"update-build-ts-version": "npm install -D typescript@next @typescript/native-preview && (cd build && npm run compile)"
|
||||
"update-build-ts-version": "npm install -D typescript@next && npm install -D @typescript/native-preview && (cd build && npm run compile)"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
@@ -142,7 +142,7 @@
|
||||
"@types/yauzl": "^2.10.0",
|
||||
"@types/yazl": "^2.4.2",
|
||||
"@typescript-eslint/utils": "^8.45.0",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20251117",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20250812.1",
|
||||
"@vscode/gulp-electron": "^1.38.2",
|
||||
"@vscode/l10n-dev": "0.0.35",
|
||||
"@vscode/telemetry-extractor": "^1.10.2",
|
||||
@@ -214,7 +214,7 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"tsec": "0.2.7",
|
||||
"tslib": "^2.6.3",
|
||||
"typescript": "^6.0.0-dev.20251117",
|
||||
"typescript": "^6.0.0-dev.20251110",
|
||||
"typescript-eslint": "^8.45.0",
|
||||
"util": "^0.12.4",
|
||||
"webpack": "^5.94.0",
|
||||
@@ -240,4 +240,4 @@
|
||||
"optionalDependencies": {
|
||||
"windows-foreground-love": "0.5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -528,7 +528,8 @@ function configureCrashReporter(): void {
|
||||
productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName,
|
||||
submitURL,
|
||||
uploadToServer,
|
||||
compress: true
|
||||
compress: true,
|
||||
ignoreSystemCrashHandler: true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -42,21 +42,12 @@
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container > .select-box-details-pane {
|
||||
padding: 5px;
|
||||
padding: 5px 6px;
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:first-child {
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:last-child {
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-text {
|
||||
|
||||
@@ -639,4 +639,5 @@ export const codiconsLibrary = {
|
||||
searchLarge: register('search-large', 0xec70),
|
||||
terminalGitBash: register('terminal-git-bash', 0xec71),
|
||||
windowActive: register('window-active', 0xec72),
|
||||
forward: register('forward', 0xec73),
|
||||
} as const;
|
||||
|
||||
@@ -213,7 +213,7 @@ export interface IProductConfiguration {
|
||||
readonly enterpriseProviderId: string;
|
||||
readonly enterpriseProviderConfig: string;
|
||||
readonly enterpriseProviderUriSetting: string;
|
||||
readonly scopes: string[];
|
||||
readonly scopes: string[][];
|
||||
};
|
||||
readonly tokenEntitlementUrl: string;
|
||||
readonly chatEntitlementUrl: string;
|
||||
|
||||
@@ -143,14 +143,16 @@ export function ltrim(haystack: string, needle: string): string {
|
||||
}
|
||||
|
||||
const needleLen = needle.length;
|
||||
if (needleLen === 0 || haystack.length === 0) {
|
||||
return haystack;
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
|
||||
while (haystack.indexOf(needle, offset) === offset) {
|
||||
offset = offset + needleLen;
|
||||
if (needleLen === 1) {
|
||||
const ch = needle.charCodeAt(0);
|
||||
while (offset < haystack.length && haystack.charCodeAt(offset) === ch) {
|
||||
offset++;
|
||||
}
|
||||
} else {
|
||||
while (haystack.startsWith(needle, offset)) {
|
||||
offset += needleLen;
|
||||
}
|
||||
}
|
||||
return haystack.substring(offset);
|
||||
}
|
||||
@@ -168,22 +170,18 @@ export function rtrim(haystack: string, needle: string): string {
|
||||
const needleLen = needle.length,
|
||||
haystackLen = haystack.length;
|
||||
|
||||
if (needleLen === 0 || haystackLen === 0) {
|
||||
return haystack;
|
||||
if (needleLen === 1) {
|
||||
let end = haystackLen;
|
||||
const ch = needle.charCodeAt(0);
|
||||
while (end > 0 && haystack.charCodeAt(end - 1) === ch) {
|
||||
end--;
|
||||
}
|
||||
return haystack.substring(0, end);
|
||||
}
|
||||
|
||||
let offset = haystackLen,
|
||||
idx = -1;
|
||||
|
||||
while (true) {
|
||||
idx = haystack.lastIndexOf(needle, offset - 1);
|
||||
if (idx === -1 || idx + needleLen !== offset) {
|
||||
break;
|
||||
}
|
||||
if (idx === 0) {
|
||||
return '';
|
||||
}
|
||||
offset = idx;
|
||||
let offset = haystackLen;
|
||||
while (offset > 0 && haystack.endsWith(needle, offset)) {
|
||||
offset -= needleLen;
|
||||
}
|
||||
|
||||
return haystack.substring(0, offset);
|
||||
@@ -1197,10 +1195,9 @@ export class AmbiguousCharacters {
|
||||
);
|
||||
});
|
||||
|
||||
private static readonly cache = new LRUCachedFunction<
|
||||
string[],
|
||||
AmbiguousCharacters
|
||||
>({ getCacheKey: JSON.stringify }, (locales) => {
|
||||
private static readonly cache = new LRUCachedFunction<string, AmbiguousCharacters>((localesStr) => {
|
||||
const locales = localesStr.split(',');
|
||||
|
||||
function arrayToMap(arr: number[]): Map<number, number> {
|
||||
const result = new Map<number, number>();
|
||||
for (let i = 0; i < arr.length; i += 2) {
|
||||
@@ -1257,8 +1254,8 @@ export class AmbiguousCharacters {
|
||||
return new AmbiguousCharacters(map);
|
||||
});
|
||||
|
||||
public static getInstance(locales: Set<string>): AmbiguousCharacters {
|
||||
return AmbiguousCharacters.cache.get(Array.from(locales));
|
||||
public static getInstance(locales: Iterable<string>): AmbiguousCharacters {
|
||||
return AmbiguousCharacters.cache.get(Array.from(locales).join(','));
|
||||
}
|
||||
|
||||
private static _locales = new Lazy<string[]>(() =>
|
||||
|
||||
@@ -215,6 +215,11 @@ suite('Strings', () => {
|
||||
assert.strictEqual(strings.ltrim('///', '/'), '');
|
||||
assert.strictEqual(strings.ltrim('', ''), '');
|
||||
assert.strictEqual(strings.ltrim('', '/'), '');
|
||||
// Multi-character needle with consecutive repetitions
|
||||
assert.strictEqual(strings.ltrim('---hello', '---'), 'hello');
|
||||
assert.strictEqual(strings.ltrim('------hello', '---'), 'hello');
|
||||
assert.strictEqual(strings.ltrim('---------hello', '---'), 'hello');
|
||||
assert.strictEqual(strings.ltrim('hello---', '---'), 'hello---');
|
||||
});
|
||||
|
||||
test('rtrim', () => {
|
||||
@@ -228,6 +233,13 @@ suite('Strings', () => {
|
||||
assert.strictEqual(strings.rtrim('///', '/'), '');
|
||||
assert.strictEqual(strings.rtrim('', ''), '');
|
||||
assert.strictEqual(strings.rtrim('', '/'), '');
|
||||
// Multi-character needle with consecutive repetitions (bug fix)
|
||||
assert.strictEqual(strings.rtrim('hello---', '---'), 'hello');
|
||||
assert.strictEqual(strings.rtrim('hello------', '---'), 'hello');
|
||||
assert.strictEqual(strings.rtrim('hello---------', '---'), 'hello');
|
||||
assert.strictEqual(strings.rtrim('---hello', '---'), '---hello');
|
||||
assert.strictEqual(strings.rtrim('hello world' + '---'.repeat(10), '---'), 'hello world');
|
||||
assert.strictEqual(strings.rtrim('path/to/file///', '//'), 'path/to/file/');
|
||||
});
|
||||
|
||||
test('trim', () => {
|
||||
|
||||
@@ -33,6 +33,7 @@ import { IME } from '../../../../../base/common/ime.js';
|
||||
import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js';
|
||||
import { ILogService, LogLevel } from '../../../../../platform/log/common/log.js';
|
||||
import { generateUuid } from '../../../../../base/common/uuid.js';
|
||||
import { inputLatency } from '../../../../../base/browser/performance.js';
|
||||
|
||||
// Corresponds to classes in nativeEditContext.css
|
||||
enum CompositionClassName {
|
||||
@@ -125,12 +126,16 @@ export class NativeEditContext extends AbstractEditContext {
|
||||
this.logService.trace('NativeEditContext#cut (before viewController.cut)');
|
||||
this._viewController.cut();
|
||||
}));
|
||||
this._register(addDisposableListener(this.domNode.domNode, 'selectionchange', () => {
|
||||
inputLatency.onSelectionChange();
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this.domNode.domNode, 'keyup', (e) => this._onKeyUp(e)));
|
||||
this._register(addDisposableListener(this.domNode.domNode, 'keydown', async (e) => this._onKeyDown(e)));
|
||||
this._register(addDisposableListener(this._imeTextArea.domNode, 'keyup', (e) => this._onKeyUp(e)));
|
||||
this._register(addDisposableListener(this._imeTextArea.domNode, 'keydown', async (e) => this._onKeyDown(e)));
|
||||
this._register(addDisposableListener(this.domNode.domNode, 'beforeinput', async (e) => {
|
||||
inputLatency.onBeforeInput();
|
||||
if (e.inputType === 'insertParagraph' || e.inputType === 'insertLineBreak') {
|
||||
this._onType(this._viewController, { text: '\n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 });
|
||||
}
|
||||
@@ -166,6 +171,7 @@ export class NativeEditContext extends AbstractEditContext {
|
||||
this._register(editContextAddDisposableListener(this._editContext, 'characterboundsupdate', (e) => this._updateCharacterBounds(e)));
|
||||
let highSurrogateCharacter: string | undefined;
|
||||
this._register(editContextAddDisposableListener(this._editContext, 'textupdate', (e) => {
|
||||
inputLatency.onInput();
|
||||
const text = e.text;
|
||||
if (text.length === 1) {
|
||||
const charCode = text.charCodeAt(0);
|
||||
@@ -355,10 +361,12 @@ export class NativeEditContext extends AbstractEditContext {
|
||||
// --- Private methods ---
|
||||
|
||||
private _onKeyUp(e: KeyboardEvent) {
|
||||
inputLatency.onKeyUp();
|
||||
this._viewController.emitKeyUp(new StandardKeyboardEvent(e));
|
||||
}
|
||||
|
||||
private _onKeyDown(e: KeyboardEvent) {
|
||||
inputLatency.onKeyDown();
|
||||
const standardKeyboardEvent = new StandardKeyboardEvent(e);
|
||||
// When the IME is visible, the keys, like arrow-left and arrow-right, should be used to navigate in the IME, and should not be propagated further
|
||||
if (standardKeyboardEvent.keyCode === KeyCode.KEY_IN_COMPOSITION) {
|
||||
|
||||
@@ -39,6 +39,6 @@
|
||||
}
|
||||
|
||||
.action-item .action-label.separator {
|
||||
background-color: var(--vscode-menu-separatorBackground);
|
||||
background-color: var(--vscode-button-separator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export class TelemetryService implements ITelemetryService {
|
||||
this._sendErrorTelemetry = !!config.sendErrorTelemetry;
|
||||
|
||||
// static cleanup pattern for: `vscode-file:///DANGEROUS/PATH/resources/app/Useful/Information`
|
||||
this._cleanupPatterns = [/(vscode-)?file:\/\/\/.*?\/resources\/app\//gi];
|
||||
this._cleanupPatterns = [/(vscode-)?file:\/\/.*?\/resources\/app\//gi];
|
||||
|
||||
for (const piiPath of this._piiPaths) {
|
||||
this._cleanupPatterns.push(new RegExp(escapeRegExpCharacters(piiPath), 'gi'));
|
||||
|
||||
@@ -743,5 +743,240 @@ suite('TelemetryService', () => {
|
||||
service.dispose();
|
||||
});
|
||||
|
||||
test('Unexpected Error Telemetry removes Windows PII but preserves code path', sinonTestFn(function (this: any) {
|
||||
const origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
|
||||
Errors.setUnexpectedErrorHandler(() => { });
|
||||
|
||||
try {
|
||||
const testAppender = new TestTelemetryAppender();
|
||||
const service = new TestErrorTelemetryService({ appenders: [testAppender] });
|
||||
const errorTelemetry = new ErrorTelemetry(service);
|
||||
|
||||
const windowsUserPath = 'c:/Users/bpasero/AppData/Local/Programs/Microsoft%20VS%20Code%20Insiders/resources/app/';
|
||||
const codePath = 'out/vs/workbench/workbench.desktop.main.js';
|
||||
const stack = [
|
||||
` at cTe.gc (vscode-file://vscode-app/${windowsUserPath}${codePath}:2724:81492)`,
|
||||
` at async cTe.setInput (vscode-file://vscode-app/${windowsUserPath}${codePath}:2724:80650)`,
|
||||
` at async qJe.S (vscode-file://vscode-app/${windowsUserPath}${codePath}:698:58520)`,
|
||||
` at async qJe.L (vscode-file://vscode-app/${windowsUserPath}${codePath}:698:57080)`,
|
||||
` at async qJe.openEditor (vscode-file://vscode-app/${windowsUserPath}${codePath}:698:56162)`
|
||||
];
|
||||
|
||||
const windowsError: any = new Error('The editor could not be opened because the file was not found.');
|
||||
windowsError.stack = stack.join('\n');
|
||||
|
||||
Errors.onUnexpectedError(windowsError);
|
||||
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
|
||||
|
||||
assert.strictEqual(testAppender.getEventsCount(), 1);
|
||||
// Verify PII (username and path) is removed
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('bpasero'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Users'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('c:/Users'), -1);
|
||||
// Verify important code path is preserved
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1);
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1);
|
||||
|
||||
errorTelemetry.dispose();
|
||||
service.dispose();
|
||||
} finally {
|
||||
Errors.setUnexpectedErrorHandler(origErrorHandler);
|
||||
}
|
||||
}));
|
||||
|
||||
test('Uncaught Error Telemetry removes Windows PII but preserves code path', sinonTestFn(function (this: any) {
|
||||
const errorStub = sinon.stub();
|
||||
mainWindow.onerror = errorStub;
|
||||
|
||||
const testAppender = new TestTelemetryAppender();
|
||||
const service = new TestErrorTelemetryService({ appenders: [testAppender] });
|
||||
const errorTelemetry = new ErrorTelemetry(service);
|
||||
|
||||
const windowsUserPath = 'c:/Users/bpasero/AppData/Local/Programs/Microsoft%20VS%20Code%20Insiders/resources/app/';
|
||||
const codePath = 'out/vs/workbench/workbench.desktop.main.js';
|
||||
const stack = [
|
||||
` at cTe.gc (vscode-file://vscode-app/${windowsUserPath}${codePath}:2724:81492)`,
|
||||
` at async cTe.setInput (vscode-file://vscode-app/${windowsUserPath}${codePath}:2724:80650)`,
|
||||
` at async qJe.S (vscode-file://vscode-app/${windowsUserPath}${codePath}:698:58520)`
|
||||
];
|
||||
|
||||
const windowsError: any = new Error('The editor could not be opened because the file was not found.');
|
||||
windowsError.stack = stack.join('\n');
|
||||
|
||||
mainWindow.onerror('The editor could not be opened because the file was not found.', 'test.js', 2, 42, windowsError);
|
||||
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
|
||||
|
||||
assert.strictEqual(errorStub.callCount, 1);
|
||||
// Verify PII (username and path) is removed
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('bpasero'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Users'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('c:/Users'), -1);
|
||||
// Verify important code path is preserved
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1);
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1);
|
||||
|
||||
errorTelemetry.dispose();
|
||||
service.dispose();
|
||||
sinon.restore();
|
||||
}));
|
||||
|
||||
test('Unexpected Error Telemetry removes macOS PII but preserves code path', sinonTestFn(function (this: any) {
|
||||
const origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
|
||||
Errors.setUnexpectedErrorHandler(() => { });
|
||||
|
||||
try {
|
||||
const testAppender = new TestTelemetryAppender();
|
||||
const service = new TestErrorTelemetryService({ appenders: [testAppender] });
|
||||
const errorTelemetry = new ErrorTelemetry(service);
|
||||
|
||||
const macUserPath = 'Applications/Visual%20Studio%20Code%20-%20Insiders.app/Contents/Resources/app/';
|
||||
const codePath = 'out/vs/workbench/workbench.desktop.main.js';
|
||||
const stack = [
|
||||
` at uTe.gc (vscode-file://vscode-app/${macUserPath}${codePath}:2720:81492)`,
|
||||
` at async uTe.setInput (vscode-file://vscode-app/${macUserPath}${codePath}:2720:80650)`,
|
||||
` at async JJe.S (vscode-file://vscode-app/${macUserPath}${codePath}:698:58520)`,
|
||||
` at async JJe.L (vscode-file://vscode-app/${macUserPath}${codePath}:698:57080)`,
|
||||
` at async JJe.openEditor (vscode-file://vscode-app/${macUserPath}${codePath}:698:56162)`
|
||||
];
|
||||
|
||||
const macError: any = new Error('The editor could not be opened because the file was not found.');
|
||||
macError.stack = stack.join('\n');
|
||||
|
||||
Errors.onUnexpectedError(macError);
|
||||
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
|
||||
|
||||
assert.strictEqual(testAppender.getEventsCount(), 1);
|
||||
// Verify PII (application path) is removed
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Applications/Visual'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Visual%20Studio%20Code'), -1);
|
||||
// Verify important code path is preserved
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1);
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1);
|
||||
|
||||
errorTelemetry.dispose();
|
||||
service.dispose();
|
||||
} finally {
|
||||
Errors.setUnexpectedErrorHandler(origErrorHandler);
|
||||
}
|
||||
}));
|
||||
|
||||
test('Uncaught Error Telemetry removes macOS PII but preserves code path', sinonTestFn(function (this: any) {
|
||||
const errorStub = sinon.stub();
|
||||
mainWindow.onerror = errorStub;
|
||||
|
||||
const testAppender = new TestTelemetryAppender();
|
||||
const service = new TestErrorTelemetryService({ appenders: [testAppender] });
|
||||
const errorTelemetry = new ErrorTelemetry(service);
|
||||
|
||||
const macUserPath = 'Applications/Visual%20Studio%20Code%20-%20Insiders.app/Contents/Resources/app/';
|
||||
const codePath = 'out/vs/workbench/workbench.desktop.main.js';
|
||||
const stack = [
|
||||
` at uTe.gc (vscode-file://vscode-app/${macUserPath}${codePath}:2720:81492)`,
|
||||
` at async uTe.setInput (vscode-file://vscode-app/${macUserPath}${codePath}:2720:80650)`,
|
||||
` at async JJe.S (vscode-file://vscode-app/${macUserPath}${codePath}:698:58520)`
|
||||
];
|
||||
|
||||
const macError: any = new Error('The editor could not be opened because the file was not found.');
|
||||
macError.stack = stack.join('\n');
|
||||
|
||||
mainWindow.onerror('The editor could not be opened because the file was not found.', 'test.js', 2, 42, macError);
|
||||
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
|
||||
|
||||
assert.strictEqual(errorStub.callCount, 1);
|
||||
// Verify PII (application path) is removed
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Applications/Visual'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('Visual%20Studio%20Code'), -1);
|
||||
// Verify important code path is preserved
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1);
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1);
|
||||
|
||||
errorTelemetry.dispose();
|
||||
service.dispose();
|
||||
sinon.restore();
|
||||
}));
|
||||
|
||||
test('Unexpected Error Telemetry removes Linux PII but preserves code path', sinonTestFn(function (this: any) {
|
||||
const origErrorHandler = Errors.errorHandler.getUnexpectedErrorHandler();
|
||||
Errors.setUnexpectedErrorHandler(() => { });
|
||||
|
||||
try {
|
||||
const testAppender = new TestTelemetryAppender();
|
||||
const service = new TestErrorTelemetryService({ appenders: [testAppender] });
|
||||
const errorTelemetry = new ErrorTelemetry(service);
|
||||
|
||||
const linuxUserPath = '/home/parallels/GitDevelopment/vscode-node-sqlite3-perf/';
|
||||
const linuxSystemPath = 'usr/share/code-insiders/resources/app/';
|
||||
const codePath = 'out/vs/workbench/workbench.desktop.main.js';
|
||||
const stack = [
|
||||
` at _kt.G (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3825:65940)`,
|
||||
` at _kt.F (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3825:65765)`,
|
||||
` at async axt.L (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3830:9998)`,
|
||||
` at async axt.readStream (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3830:9773)`,
|
||||
` at async mye.Eb (vscode-file://vscode-app/${linuxSystemPath}${codePath}:1313:12359)`
|
||||
];
|
||||
|
||||
const linuxError: any = new Error(`Invalid fake file 'git:${linuxUserPath}index.js.git?{"path":"${linuxUserPath}index.js","ref":""}' (Canceled: Canceled)`);
|
||||
linuxError.stack = stack.join('\n');
|
||||
|
||||
Errors.onUnexpectedError(linuxError);
|
||||
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
|
||||
|
||||
assert.strictEqual(testAppender.getEventsCount(), 1);
|
||||
// Verify PII (username and home directory) is removed
|
||||
assert.strictEqual(testAppender.events[0].data.msg.indexOf('parallels'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.msg.indexOf('/home/parallels'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.msg.indexOf('GitDevelopment'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('parallels'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('/home/parallels'), -1);
|
||||
// Verify important code path is preserved
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1);
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1);
|
||||
|
||||
errorTelemetry.dispose();
|
||||
service.dispose();
|
||||
} finally {
|
||||
Errors.setUnexpectedErrorHandler(origErrorHandler);
|
||||
}
|
||||
}));
|
||||
|
||||
test('Uncaught Error Telemetry removes Linux PII but preserves code path', sinonTestFn(function (this: any) {
|
||||
const errorStub = sinon.stub();
|
||||
mainWindow.onerror = errorStub;
|
||||
|
||||
const testAppender = new TestTelemetryAppender();
|
||||
const service = new TestErrorTelemetryService({ appenders: [testAppender] });
|
||||
const errorTelemetry = new ErrorTelemetry(service);
|
||||
|
||||
const linuxUserPath = '/home/parallels/GitDevelopment/vscode-node-sqlite3-perf/';
|
||||
const linuxSystemPath = 'usr/share/code-insiders/resources/app/';
|
||||
const codePath = 'out/vs/workbench/workbench.desktop.main.js';
|
||||
const stack = [
|
||||
` at _kt.G (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3825:65940)`,
|
||||
` at _kt.F (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3825:65765)`,
|
||||
` at async axt.L (vscode-file://vscode-app/${linuxSystemPath}${codePath}:3830:9998)`
|
||||
];
|
||||
|
||||
const linuxError: any = new Error(`Unable to read file 'git:${linuxUserPath}index.js.git'`);
|
||||
linuxError.stack = stack.join('\n');
|
||||
|
||||
mainWindow.onerror(`Unable to read file 'git:${linuxUserPath}index.js.git'`, 'test.js', 2, 42, linuxError);
|
||||
this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT);
|
||||
|
||||
assert.strictEqual(errorStub.callCount, 1);
|
||||
// Verify PII (username and home directory) is removed
|
||||
assert.strictEqual(testAppender.events[0].data.msg.indexOf('parallels'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.msg.indexOf('/home/parallels'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.msg.indexOf('GitDevelopment'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('parallels'), -1);
|
||||
assert.strictEqual(testAppender.events[0].data.callstack.indexOf('/home/parallels'), -1);
|
||||
// Verify important code path is preserved
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf(codePath), -1);
|
||||
assert.notStrictEqual(testAppender.events[0].data.callstack.indexOf('out/vs/workbench'), -1);
|
||||
|
||||
errorTelemetry.dispose();
|
||||
service.dispose();
|
||||
sinon.restore();
|
||||
}));
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
});
|
||||
|
||||
@@ -180,8 +180,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||
try {
|
||||
return await this._proxy.$invokeAgent(handle, request, {
|
||||
history,
|
||||
chatSessionContext: chatSession?.contributedChatSession,
|
||||
chatSummary: request.chatSummary
|
||||
chatSessionContext: chatSession?.contributedChatSession
|
||||
}, token) ?? {};
|
||||
} finally {
|
||||
this._pendingProgress.delete(request.requestId);
|
||||
|
||||
@@ -15,7 +15,7 @@ import { URI } from '../../../base/common/uri.js';
|
||||
@extHostNamedCustomer(MainContext.MainThreadChatContext)
|
||||
export class MainThreadChatContext extends Disposable implements MainThreadChatContextShape {
|
||||
private readonly _proxy: ExtHostChatContextShape;
|
||||
private readonly _providers = new Map<number, { id: string; selector: IDocumentFilterDto[]; support: IChatContextSupport }>();
|
||||
private readonly _providers = new Map<number, { id: string; selector: IDocumentFilterDto[] | undefined; support: IChatContextSupport }>();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@@ -25,7 +25,7 @@ export class MainThreadChatContext extends Disposable implements MainThreadChatC
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatContext);
|
||||
}
|
||||
|
||||
$registerChatContextProvider(handle: number, id: string, selector: IDocumentFilterDto[], _options: { icon: ThemeIcon }, support: IChatContextSupport): void {
|
||||
$registerChatContextProvider(handle: number, id: string, selector: IDocumentFilterDto[] | undefined, _options: { icon: ThemeIcon }, support: IChatContextSupport): void {
|
||||
this._providers.set(handle, { selector, support, id });
|
||||
this._chatContextService.registerChatContextProvider(id, selector, {
|
||||
provideChatContext: (token: CancellationToken) => {
|
||||
@@ -48,4 +48,12 @@ export class MainThreadChatContext extends Disposable implements MainThreadChatC
|
||||
this._chatContextService.unregisterChatContextProvider(provider.id);
|
||||
this._providers.delete(handle);
|
||||
}
|
||||
|
||||
$updateWorkspaceContextItems(handle: number, items: IChatContextItem[]): void {
|
||||
const provider = this._providers.get(handle);
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
this._chatContextService.updateWorkspaceContextItems(provider.id, items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1537,9 +1537,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension, 'chatOutputRenderer');
|
||||
return extHostChatOutputRenderer.registerChatOutputRenderer(extension, viewType, renderer);
|
||||
},
|
||||
registerChatContextProvider(selector: vscode.DocumentSelector, id: string, provider: vscode.ChatContextProvider): vscode.Disposable {
|
||||
registerChatContextProvider(selector: vscode.DocumentSelector | undefined, id: string, provider: vscode.ChatContextProvider): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension, 'chatContextProvider');
|
||||
return extHostChatContext.registerChatContextProvider(checkSelector(selector), `${extension.id}-${id}`, provider);
|
||||
return extHostChatContext.registerChatContextProvider(selector ? checkSelector(selector) : undefined, `${extension.id}-${id}`, provider);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1329,8 +1329,9 @@ export interface ExtHostChatContextShape {
|
||||
}
|
||||
|
||||
export interface MainThreadChatContextShape extends IDisposable {
|
||||
$registerChatContextProvider(handle: number, id: string, selector: IDocumentFilterDto[], options: {}, support: IChatContextSupport): void;
|
||||
$registerChatContextProvider(handle: number, id: string, selector: IDocumentFilterDto[] | undefined, options: {}, support: IChatContextSupport): void;
|
||||
$unregisterChatContextProvider(handle: number): void;
|
||||
$updateWorkspaceContextItems(handle: number, items: IChatContextItem[]): void;
|
||||
}
|
||||
|
||||
export interface MainThreadEmbeddingsShape extends IDisposable {
|
||||
@@ -1420,7 +1421,7 @@ export interface IChatSessionContextDto {
|
||||
}
|
||||
|
||||
export interface ExtHostChatAgentsShape2 {
|
||||
$invokeAgent(handle: number, request: Dto<IChatAgentRequest>, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContextDto; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise<IChatAgentResult | undefined>;
|
||||
$invokeAgent(handle: number, request: Dto<IChatAgentRequest>, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContextDto }, token: CancellationToken): Promise<IChatAgentResult | undefined>;
|
||||
$provideFollowups(request: Dto<IChatAgentRequest>, handle: number, result: IChatAgentResult, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise<IChatFollowup[]>;
|
||||
$acceptFeedback(handle: number, result: IChatAgentResult, voteAction: IChatVoteAction): void;
|
||||
$acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void;
|
||||
|
||||
@@ -559,7 +559,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
|
||||
this._onDidChangeChatRequestTools.fire(request.extRequest);
|
||||
}
|
||||
|
||||
async $invokeAgent(handle: number, requestDto: Dto<IChatAgentRequest>, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContextDto; chatSummary?: { prompt?: string; history?: string } }, token: CancellationToken): Promise<IChatAgentResult | undefined> {
|
||||
async $invokeAgent(handle: number, requestDto: Dto<IChatAgentRequest>, context: { history: IChatAgentHistoryEntryDto[]; chatSessionContext?: IChatSessionContextDto }, token: CancellationToken): Promise<IChatAgentResult | undefined> {
|
||||
const agent = this._agents.get(handle);
|
||||
if (!agent) {
|
||||
throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`);
|
||||
@@ -606,11 +606,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
|
||||
};
|
||||
}
|
||||
|
||||
const chatContext: vscode.ChatContext = {
|
||||
history,
|
||||
chatSessionContext,
|
||||
chatSummary: context.chatSummary
|
||||
};
|
||||
const chatContext: vscode.ChatContext = { history, chatSessionContext };
|
||||
const task = agent.invoke(
|
||||
extRequest,
|
||||
chatContext,
|
||||
|
||||
@@ -10,18 +10,20 @@ import { ExtHostChatContextShape, MainContext, MainThreadChatContextShape } from
|
||||
import { DocumentSelector } from './extHostTypeConverters.js';
|
||||
import { IExtHostRpcService } from './extHostRpcService.js';
|
||||
import { IChatContextItem } from '../../contrib/chat/common/chatContext.js';
|
||||
import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
|
||||
|
||||
export class ExtHostChatContext implements ExtHostChatContextShape {
|
||||
export class ExtHostChatContext extends Disposable implements ExtHostChatContextShape {
|
||||
declare _serviceBrand: undefined;
|
||||
|
||||
private _proxy: MainThreadChatContextShape;
|
||||
private _handlePool: number = 0;
|
||||
private _providers: Map<number, vscode.ChatContextProvider> = new Map();
|
||||
private _providers: Map<number, { provider: vscode.ChatContextProvider; disposables: DisposableStore }> = new Map();
|
||||
private _itemPool: number = 0;
|
||||
private _items: Map<number, Map<number, vscode.ChatContextItem>> = new Map(); // handle -> itemHandle -> item
|
||||
|
||||
constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostRpc.getProxy(MainContext.MainThreadChatContext);
|
||||
}
|
||||
|
||||
@@ -83,16 +85,7 @@ export class ExtHostChatContext implements ExtHostChatContextShape {
|
||||
return item;
|
||||
}
|
||||
|
||||
async $resolveChatContext(handle: number, context: IChatContextItem, token: CancellationToken): Promise<IChatContextItem> {
|
||||
const provider = this._getProvider(handle);
|
||||
|
||||
if (!provider.resolveChatContext) {
|
||||
throw new Error('resolveChatContext not implemented');
|
||||
}
|
||||
const extItem = this._items.get(handle)?.get(context.handle);
|
||||
if (!extItem) {
|
||||
throw new Error('Chat context item not found');
|
||||
}
|
||||
private async _doResolve(provider: vscode.ChatContextProvider, context: IChatContextItem, extItem: vscode.ChatContextItem, token: CancellationToken): Promise<IChatContextItem> {
|
||||
const extResult = await provider.resolveChatContext(extItem, token);
|
||||
const result = extResult ?? context;
|
||||
return {
|
||||
@@ -104,23 +97,69 @@ export class ExtHostChatContext implements ExtHostChatContextShape {
|
||||
};
|
||||
}
|
||||
|
||||
registerChatContextProvider(selector: vscode.DocumentSelector, id: string, provider: vscode.ChatContextProvider): vscode.Disposable {
|
||||
async $resolveChatContext(handle: number, context: IChatContextItem, token: CancellationToken): Promise<IChatContextItem> {
|
||||
const provider = this._getProvider(handle);
|
||||
|
||||
if (!provider.resolveChatContext) {
|
||||
throw new Error('resolveChatContext not implemented');
|
||||
}
|
||||
const extItem = this._items.get(handle)?.get(context.handle);
|
||||
if (!extItem) {
|
||||
throw new Error('Chat context item not found');
|
||||
}
|
||||
return this._doResolve(provider, context, extItem, token);
|
||||
}
|
||||
|
||||
registerChatContextProvider(selector: vscode.DocumentSelector | undefined, id: string, provider: vscode.ChatContextProvider): vscode.Disposable {
|
||||
const handle = this._handlePool++;
|
||||
this._providers.set(handle, provider);
|
||||
this._proxy.$registerChatContextProvider(handle, `${id}`, DocumentSelector.from(selector), {}, { supportsResource: !!provider.provideChatContextForResource, supportsResolve: !!provider.resolveChatContext });
|
||||
const disposables = new DisposableStore();
|
||||
this._listenForWorkspaceContextChanges(handle, provider, disposables);
|
||||
this._providers.set(handle, { provider, disposables });
|
||||
this._proxy.$registerChatContextProvider(handle, `${id}`, selector ? DocumentSelector.from(selector) : undefined, {}, { supportsResource: !!provider.provideChatContextForResource, supportsResolve: !!provider.resolveChatContext });
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this._providers.delete(handle);
|
||||
this._proxy.$unregisterChatContextProvider(handle);
|
||||
disposables.dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private _listenForWorkspaceContextChanges(handle: number, provider: vscode.ChatContextProvider, disposables: DisposableStore): void {
|
||||
if (!provider.onDidChangeWorkspaceChatContext || !provider.provideWorkspaceChatContext) {
|
||||
return;
|
||||
}
|
||||
disposables.add(provider.onDidChangeWorkspaceChatContext(async () => {
|
||||
const workspaceContexts = await provider.provideWorkspaceChatContext!(CancellationToken.None);
|
||||
const resolvedContexts: IChatContextItem[] = [];
|
||||
for (const item of workspaceContexts ?? []) {
|
||||
const contextItem: IChatContextItem = {
|
||||
icon: item.icon,
|
||||
label: item.label,
|
||||
modelDescription: item.modelDescription,
|
||||
value: item.value,
|
||||
handle: this._itemPool++
|
||||
};
|
||||
const resolved = await this._doResolve(provider, contextItem, item, CancellationToken.None);
|
||||
resolvedContexts.push(resolved);
|
||||
}
|
||||
|
||||
this._proxy.$updateWorkspaceContextItems(handle, resolvedContexts);
|
||||
}));
|
||||
}
|
||||
|
||||
private _getProvider(handle: number): vscode.ChatContextProvider {
|
||||
if (!this._providers.has(handle)) {
|
||||
throw new Error('Chat context provider not found');
|
||||
}
|
||||
return this._providers.get(handle)!;
|
||||
return this._providers.get(handle)!.provider;
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
super.dispose();
|
||||
for (const { disposables } of this._providers.values()) {
|
||||
disposables.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ export class ChatContinueInSessionActionItem extends ActionWidgetDropdownActionV
|
||||
}
|
||||
|
||||
protected override renderLabel(element: HTMLElement): IDisposable | null {
|
||||
const icon = this.contextKeyService.contextMatchesRules(ChatContextKeys.remoteJobCreating) ? Codicon.sync : Codicon.indent;
|
||||
const icon = this.contextKeyService.contextMatchesRules(ChatContextKeys.remoteJobCreating) ? Codicon.sync : Codicon.forward;
|
||||
element.classList.add(...ThemeIcon.asClassNameArray(icon));
|
||||
|
||||
return super.renderLabel(element);
|
||||
|
||||
@@ -11,11 +11,12 @@ import { IMarkdownString } from '../../../../../base/common/htmlContent.js';
|
||||
import { Disposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { ResourceMap } from '../../../../../base/common/map.js';
|
||||
import { ThemeIcon } from '../../../../../base/common/themables.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { URI, UriComponents } from '../../../../../base/common/uri.js';
|
||||
import { MenuId } from '../../../../../platform/actions/common/actions.js';
|
||||
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
|
||||
import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';
|
||||
import { ChatSessionStatus, IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js';
|
||||
import { ChatSessionStatus, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js';
|
||||
import { AgentSessionProviders, getAgentSessionProviderIcon, getAgentSessionProviderName } from './agentSessions.js';
|
||||
import { AgentSessionsViewFilter } from './agentSessionsViewFilter.js';
|
||||
|
||||
@@ -35,7 +36,7 @@ export interface IAgentSessionsViewModel {
|
||||
|
||||
export interface IAgentSessionViewModel {
|
||||
|
||||
readonly provider: IChatSessionItemProvider;
|
||||
readonly providerType: string;
|
||||
readonly providerLabel: string;
|
||||
|
||||
readonly resource: URI;
|
||||
@@ -65,7 +66,7 @@ export interface IAgentSessionViewModel {
|
||||
}
|
||||
|
||||
export function isLocalAgentSessionItem(session: IAgentSessionViewModel): boolean {
|
||||
return session.provider.chatSessionType === localChatSessionType;
|
||||
return session.providerType === localChatSessionType;
|
||||
}
|
||||
|
||||
export function isAgentSession(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionViewModel {
|
||||
@@ -114,20 +115,25 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
}>();
|
||||
|
||||
private readonly filter: AgentSessionsViewFilter;
|
||||
private readonly cache: AgentSessionsCache;
|
||||
|
||||
constructor(
|
||||
options: IAgentSessionsViewModelOptions,
|
||||
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.filter = this._register(this.instantiationService.createInstance(AgentSessionsViewFilter, { filterMenuId: options.filterMenuId }));
|
||||
|
||||
this.registerListeners();
|
||||
this.cache = this.instantiationService.createInstance(AgentSessionsCache);
|
||||
this._sessions = this.cache.loadCachedSessions();
|
||||
|
||||
this.resolve(undefined);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
@@ -135,6 +141,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
this._register(this.chatSessionsService.onDidChangeAvailability(() => this.resolve(undefined)));
|
||||
this._register(this.chatSessionsService.onDidChangeSessionItems(provider => this.resolve(provider)));
|
||||
this._register(this.filter.onDidChange(() => this._onDidChangeSessions.fire()));
|
||||
this._register(this.storageService.onWillSaveState(() => this.cache.saveCachedSessions(this._sessions)));
|
||||
}
|
||||
|
||||
async resolve(provider: string | string[] | undefined): Promise<void> {
|
||||
@@ -169,19 +176,16 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
mapSessionContributionToType.set(contribution.type, contribution);
|
||||
}
|
||||
|
||||
const resolvedProviders = new Set<string>();
|
||||
const sessions = new ResourceMap<IAgentSessionViewModel>();
|
||||
for (const provider of this.chatSessionsService.getAllChatSessionItemProviders()) {
|
||||
if (!providersToResolve.includes(undefined) && !providersToResolve.includes(provider.chatSessionType)) {
|
||||
for (const session of this._sessions) {
|
||||
if (session.provider.chatSessionType === provider.chatSessionType) {
|
||||
sessions.set(session.resource, session);
|
||||
}
|
||||
}
|
||||
|
||||
continue; // skipped for resolving, preserve existing ones
|
||||
continue; // skip: not considered for resolving
|
||||
}
|
||||
|
||||
const providerSessions = await provider.provideChatSessionItems(token);
|
||||
resolvedProviders.add(provider.chatSessionType);
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
@@ -240,7 +244,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
}
|
||||
|
||||
sessions.set(session.resource, {
|
||||
provider,
|
||||
providerType: provider.chatSessionType,
|
||||
providerLabel,
|
||||
resource: session.resource,
|
||||
label: session.label,
|
||||
@@ -260,6 +264,12 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
}
|
||||
}
|
||||
|
||||
for (const session of this._sessions) {
|
||||
if (!resolvedProviders.has(session.providerType)) {
|
||||
sessions.set(session.resource, session); // fill in existing sessions for providers that did not resolve
|
||||
}
|
||||
}
|
||||
|
||||
this._sessions.length = 0;
|
||||
this._sessions.push(...sessions.values());
|
||||
|
||||
@@ -272,3 +282,111 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
this._onDidChangeSessions.fire();
|
||||
}
|
||||
}
|
||||
|
||||
//#region Sessions Cache
|
||||
|
||||
interface ISerializedAgentSessionViewModel {
|
||||
|
||||
readonly providerType: string;
|
||||
readonly providerLabel: string;
|
||||
|
||||
readonly resource: UriComponents;
|
||||
|
||||
readonly icon: string;
|
||||
|
||||
readonly label: string;
|
||||
|
||||
readonly description?: string | IMarkdownString;
|
||||
readonly tooltip?: string | IMarkdownString;
|
||||
|
||||
readonly status: ChatSessionStatus;
|
||||
readonly archived: boolean;
|
||||
|
||||
readonly timing: {
|
||||
readonly startTime: number;
|
||||
readonly endTime?: number;
|
||||
};
|
||||
|
||||
readonly statistics?: {
|
||||
readonly files: number;
|
||||
readonly insertions: number;
|
||||
readonly deletions: number;
|
||||
};
|
||||
}
|
||||
|
||||
class AgentSessionsCache {
|
||||
|
||||
private static readonly STORAGE_KEY = 'agentSessions.cache';
|
||||
|
||||
constructor(@IStorageService private readonly storageService: IStorageService) { }
|
||||
|
||||
saveCachedSessions(sessions: IAgentSessionViewModel[]): void {
|
||||
const serialized: ISerializedAgentSessionViewModel[] = sessions
|
||||
.filter(session =>
|
||||
// Only consider providers that we own where we know that
|
||||
// we can also invalidate the data after startup
|
||||
// Other providers are bound to a different lifecycle (extensions)
|
||||
session.providerType === AgentSessionProviders.Local ||
|
||||
session.providerType === AgentSessionProviders.Background ||
|
||||
session.providerType === AgentSessionProviders.Cloud
|
||||
)
|
||||
.map(session => ({
|
||||
providerType: session.providerType,
|
||||
providerLabel: session.providerLabel,
|
||||
|
||||
resource: session.resource.toJSON(),
|
||||
|
||||
icon: session.icon.id,
|
||||
label: session.label,
|
||||
description: session.description,
|
||||
tooltip: session.tooltip,
|
||||
|
||||
status: session.status,
|
||||
archived: session.archived,
|
||||
|
||||
timing: {
|
||||
startTime: session.timing.startTime,
|
||||
endTime: session.timing.endTime,
|
||||
},
|
||||
|
||||
statistics: session.statistics,
|
||||
}));
|
||||
this.storageService.store(AgentSessionsCache.STORAGE_KEY, JSON.stringify(serialized), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
loadCachedSessions(): IAgentSessionViewModel[] {
|
||||
const sessionsCache = this.storageService.get(AgentSessionsCache.STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
if (!sessionsCache) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const cached = JSON.parse(sessionsCache) as ISerializedAgentSessionViewModel[];
|
||||
return cached.map(session => ({
|
||||
providerType: session.providerType,
|
||||
providerLabel: session.providerLabel,
|
||||
|
||||
resource: URI.revive(session.resource),
|
||||
|
||||
icon: ThemeIcon.fromId(session.icon),
|
||||
label: session.label,
|
||||
description: session.description,
|
||||
tooltip: session.tooltip,
|
||||
|
||||
status: session.status,
|
||||
archived: session.archived,
|
||||
|
||||
timing: {
|
||||
startTime: session.timing.startTime,
|
||||
endTime: session.timing.endTime,
|
||||
},
|
||||
|
||||
statistics: session.statistics,
|
||||
}));
|
||||
} catch {
|
||||
return []; // invalid data in storage, fallback to empty sessions list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -8,15 +8,17 @@ import { localize, localize2 } from '../../../../../nls.js';
|
||||
import { IAgentSessionViewModel } from './agentSessionViewModel.js';
|
||||
import { Action, IAction } from '../../../../../base/common/actions.js';
|
||||
import { ActionViewItem, IActionViewItemOptions } from '../../../../../base/browser/ui/actionbar/actionViewItems.js';
|
||||
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
|
||||
import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js';
|
||||
import { EventHelper, h, hide, show } from '../../../../../base/browser/dom.js';
|
||||
import { assertReturnsDefined } from '../../../../../base/common/types.js';
|
||||
import { ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';
|
||||
import { Codicon } from '../../../../../base/common/codicons.js';
|
||||
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
|
||||
import { ViewAction } from '../../../../browser/parts/views/viewPane.js';
|
||||
import { AGENT_SESSIONS_VIEW_ID } from './agentSessions.js';
|
||||
import { AGENT_SESSIONS_VIEW_ID, AgentSessionProviders } from './agentSessions.js';
|
||||
import { AgentSessionsView } from './agentSessionsView.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { IChatService } from '../../common/chatService.js';
|
||||
|
||||
//#region Diff Statistics Action
|
||||
|
||||
@@ -103,10 +105,17 @@ export class AgentSessionDiffActionViewItem extends ActionViewItem {
|
||||
|
||||
const session = this.action.getSession();
|
||||
|
||||
this.commandService.executeCommand(`agentSession.${session.provider.chatSessionType}.openChanges`, this.action.getSession().resource);
|
||||
this.commandService.executeCommand(`agentSession.${session.providerType}.openChanges`, this.action.getSession().resource);
|
||||
}
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand(`agentSession.${AgentSessionProviders.Local}.openChanges`, async (accessor: ServicesAccessor, resource: URI) => {
|
||||
const chatService = accessor.get(IChatService);
|
||||
|
||||
const session = chatService.getSession(resource);
|
||||
session?.editingSession?.show();
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region View Actions
|
||||
|
||||
@@ -84,7 +84,9 @@ export class AgentSessionsView extends ViewPane {
|
||||
container.classList.add('agent-sessions-view');
|
||||
|
||||
// New Session
|
||||
this.createNewSessionButton(container);
|
||||
if (!this.configurationService.getValue('chat.hideNewButtonInAgentSessionsView')) {
|
||||
this.createNewSessionButton(container);
|
||||
}
|
||||
|
||||
// Sessions List
|
||||
this.createList(container);
|
||||
@@ -144,18 +146,22 @@ export class AgentSessionsView extends ViewPane {
|
||||
...e.editorOptions,
|
||||
};
|
||||
|
||||
await this.chatSessionsService.activateChatSessionItemProvider(session.providerType); // ensure provider is activated before trying to open
|
||||
|
||||
const group = e.sideBySide ? SIDE_GROUP : undefined;
|
||||
await this.chatWidgetService.openSession(session.resource, group, options);
|
||||
}
|
||||
|
||||
private showContextMenu({ element: session, anchor }: ITreeContextMenuEvent<IAgentSessionViewModel>): void {
|
||||
private async showContextMenu({ element: session, anchor }: ITreeContextMenuEvent<IAgentSessionViewModel>): Promise<void> {
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = await this.chatSessionsService.activateChatSessionItemProvider(session.providerType);
|
||||
|
||||
const menu = this.menuService.createMenu(MenuId.ChatSessionsMenu, this.contextKeyService.createOverlay(getSessionItemContextOverlay(
|
||||
session,
|
||||
session.provider,
|
||||
provider,
|
||||
this.chatWidgetService,
|
||||
this.chatService,
|
||||
this.editorGroupsService
|
||||
|
||||
@@ -232,7 +232,7 @@ export class AgentSessionsViewFilter extends Disposable {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.excludes.providers.includes(session.provider.chatSessionType)) {
|
||||
if (this.excludes.providers.includes(session.providerType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
}
|
||||
})()
|
||||
}
|
||||
}))
|
||||
}), { groupId: 'agent.sessions' })
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -336,13 +336,13 @@ export class AgentSessionsCompressionDelegate implements ITreeCompressionDelegat
|
||||
export class AgentSessionsSorter implements ITreeSorter<IAgentSessionViewModel> {
|
||||
|
||||
compare(sessionA: IAgentSessionViewModel, sessionB: IAgentSessionViewModel): number {
|
||||
const aHasEndTime = !!sessionA.timing.endTime;
|
||||
const bHasEndTime = !!sessionB.timing.endTime;
|
||||
const aInProgress = sessionA.status === ChatSessionStatus.InProgress;
|
||||
const bInProgress = sessionB.status === ChatSessionStatus.InProgress;
|
||||
|
||||
if (!aHasEndTime && bHasEndTime) {
|
||||
if (aInProgress && !bInProgress) {
|
||||
return -1; // a (in-progress) comes before b (finished)
|
||||
}
|
||||
if (aHasEndTime && !bHasEndTime) {
|
||||
if (!aInProgress && bInProgress) {
|
||||
return 1; // a (finished) comes after b (in-progress)
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,9 @@ import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsCo
|
||||
import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js';
|
||||
import { ChatWidgetService } from './chatWidgetService.js';
|
||||
|
||||
const toolReferenceNameEnumValues: string[] = [];
|
||||
const toolReferenceNameEnumDescriptions: string[] = [];
|
||||
|
||||
// Register configuration
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
@@ -269,7 +272,7 @@ configurationRegistry.registerConfiguration({
|
||||
'**/.vscode/*.json': false,
|
||||
'**/.git/**': false,
|
||||
'**/{package.json,package-lock.json,server.xml,build.rs,web.config,.gitattributes,.env}': false,
|
||||
'**/*.{csproj,fsproj,vbproj,vcxproj,proj,targets,props}': false,
|
||||
'**/*.{code-workspace,csproj,fsproj,vbproj,vcxproj,proj,targets,props}': false,
|
||||
},
|
||||
markdownDescription: nls.localize('chat.tools.autoApprove.edits', "Controls whether edits made by chat are automatically approved. The default is to approve all edits except those made to certain files which have the potential to cause immediate unintended side-effects, such as `**/.vscode/*.json`.\n\nSet to `true` to automatically approve edits to matching files, `false` to always require explicit approval. The last pattern matching a given file will determine whether the edit is automatically approved."),
|
||||
type: 'object',
|
||||
@@ -298,6 +301,10 @@ configurationRegistry.registerConfiguration({
|
||||
default: {},
|
||||
markdownDescription: nls.localize('chat.tools.eligibleForAutoApproval', 'Controls which tools are eligible for automatic approval. Tools set to \'false\' will always present a confirmation and will never offer the option to auto-approve. The default behavior (or setting a tool to \'true\') may result in the tool offering auto-approval options.'),
|
||||
type: 'object',
|
||||
propertyNames: {
|
||||
enum: toolReferenceNameEnumValues,
|
||||
enumDescriptions: toolReferenceNameEnumDescriptions,
|
||||
},
|
||||
additionalProperties: {
|
||||
type: 'boolean',
|
||||
},
|
||||
@@ -765,6 +772,12 @@ configurationRegistry.registerConfiguration({
|
||||
mode: 'auto'
|
||||
}
|
||||
},
|
||||
'chat.hideNewButtonInAgentSessionsView': { // TODO@bpasero remove me eventually
|
||||
type: 'boolean',
|
||||
description: nls.localize('chat.hideNewButtonInAgentSessionsView', "Controls whether the new session button is hidden in the Agent Sessions view."),
|
||||
default: false,
|
||||
tags: ['preview']
|
||||
},
|
||||
'chat.signInWithAlternateScopes': { // TODO@bpasero remove me eventually
|
||||
type: 'boolean',
|
||||
description: nls.localize('chat.signInWithAlternateScopes', "Controls whether sign-in with alternate scopes is used."),
|
||||
@@ -915,6 +928,43 @@ class ChatAgentSettingContribution extends Disposable implements IWorkbenchContr
|
||||
}
|
||||
}
|
||||
|
||||
class ToolReferenceNamesContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
static readonly ID = 'workbench.contrib.toolReferenceNames';
|
||||
|
||||
constructor(
|
||||
@ILanguageModelToolsService private readonly _languageModelToolsService: ILanguageModelToolsService,
|
||||
) {
|
||||
super();
|
||||
this._updateToolReferenceNames();
|
||||
this._register(this._languageModelToolsService.onDidChangeTools(() => this._updateToolReferenceNames()));
|
||||
}
|
||||
|
||||
private _updateToolReferenceNames(): void {
|
||||
const tools =
|
||||
Array.from(this._languageModelToolsService.getTools())
|
||||
.filter((tool): tool is typeof tool & { toolReferenceName: string } => typeof tool.toolReferenceName === 'string')
|
||||
.sort((a, b) => a.toolReferenceName.localeCompare(b.toolReferenceName));
|
||||
toolReferenceNameEnumValues.length = 0;
|
||||
toolReferenceNameEnumDescriptions.length = 0;
|
||||
for (const tool of tools) {
|
||||
toolReferenceNameEnumValues.push(tool.toolReferenceName);
|
||||
toolReferenceNameEnumDescriptions.push(nls.localize(
|
||||
'chat.toolReferenceName.description',
|
||||
"{0} - {1}",
|
||||
tool.toolReferenceName,
|
||||
tool.userDescription || tool.displayName
|
||||
));
|
||||
}
|
||||
configurationRegistry.notifyConfigurationSchemaUpdated({
|
||||
id: 'chatSidebar',
|
||||
properties: {
|
||||
[ChatConfiguration.EligibleForAutoApproval]: {}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
AccessibleViewRegistry.register(new ChatTerminalOutputAccessibleView());
|
||||
AccessibleViewRegistry.register(new ChatResponseAccessibleView());
|
||||
AccessibleViewRegistry.register(new PanelChatAccessibilityHelp());
|
||||
@@ -1019,6 +1069,7 @@ registerWorkbenchContribution2(ChatTeardownContribution.ID, ChatTeardownContribu
|
||||
registerWorkbenchContribution2(ChatStatusBarEntry.ID, ChatStatusBarEntry, WorkbenchPhase.BlockRestore);
|
||||
registerWorkbenchContribution2(BuiltinToolsContribution.ID, BuiltinToolsContribution, WorkbenchPhase.Eventually);
|
||||
registerWorkbenchContribution2(ChatAgentSettingContribution.ID, ChatAgentSettingContribution, WorkbenchPhase.AfterRestored);
|
||||
registerWorkbenchContribution2(ToolReferenceNamesContribution.ID, ToolReferenceNamesContribution, WorkbenchPhase.AfterRestored);
|
||||
registerWorkbenchContribution2(ChatEditingEditorAccessibility.ID, ChatEditingEditorAccessibility, WorkbenchPhase.AfterRestored);
|
||||
registerWorkbenchContribution2(ChatEditingEditorOverlay.ID, ChatEditingEditorOverlay, WorkbenchPhase.AfterRestored);
|
||||
registerWorkbenchContribution2(SimpleBrowserOverlay.ID, SimpleBrowserOverlay, WorkbenchPhase.AfterRestored);
|
||||
|
||||
+4
-1
@@ -11,7 +11,7 @@ import { URI } from '../../../../../base/common/uri.js';
|
||||
import { Range } from '../../../../../editor/common/core/range.js';
|
||||
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { ResourceLabels } from '../../../../browser/labels.js';
|
||||
import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isTerminalVariableEntry, OmittedState } from '../../common/chatVariableEntries.js';
|
||||
import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isTerminalVariableEntry, isWorkspaceVariableEntry, OmittedState } from '../../common/chatVariableEntries.js';
|
||||
import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js';
|
||||
import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, TerminalCommandAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../chatAttachmentWidgets.js';
|
||||
|
||||
@@ -153,6 +153,9 @@ export class ChatAttachmentsContentPart extends Disposable {
|
||||
widget = this.instantiationService.createInstance(SCMHistoryItemChangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels);
|
||||
} else if (isSCMHistoryItemChangeRangeVariableEntry(attachment)) {
|
||||
widget = this.instantiationService.createInstance(SCMHistoryItemChangeRangeAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels);
|
||||
} else if (isWorkspaceVariableEntry(attachment)) {
|
||||
// skip workspace attachments
|
||||
return;
|
||||
} else {
|
||||
widget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, correspondingContentReference, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta
|
||||
import { IChatContextPicker, IChatContextPickerItem, IChatContextPickService } from './chatContextPickService.js';
|
||||
import { IChatContextItem, IChatContextProvider } from '../common/chatContext.js';
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { IGenericChatRequestVariableEntry, StringChatContextValue } from '../common/chatVariableEntries.js';
|
||||
import { IChatRequestWorkspaceVariableEntry, IGenericChatRequestVariableEntry, StringChatContextValue } from '../common/chatVariableEntries.js';
|
||||
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { Disposable, DisposableMap, IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
@@ -22,7 +22,7 @@ export interface IChatContextService extends ChatContextService { }
|
||||
interface IChatContextProviderEntry {
|
||||
picker?: { title: string; icon: ThemeIcon };
|
||||
chatContextProvider?: {
|
||||
selector: LanguageSelector;
|
||||
selector: LanguageSelector | undefined;
|
||||
provider: IChatContextProvider;
|
||||
};
|
||||
}
|
||||
@@ -31,6 +31,7 @@ export class ChatContextService extends Disposable {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly _providers = new Map<string, IChatContextProviderEntry>();
|
||||
private readonly _workspaceContext = new Map<string, IChatContextItem[]>();
|
||||
private readonly _registeredPickers = this._register(new DisposableMap<string, IDisposable>());
|
||||
private _lastResourceContext: Map<StringChatContextValue, { originalItem: IChatContextItem; provider: IChatContextProvider }> = new Map();
|
||||
|
||||
@@ -56,7 +57,7 @@ export class ChatContextService extends Disposable {
|
||||
this._registeredPickers.set(id, this._contextPickService.registerChatContextItem(this._asPicker(providerEntry.picker.title, providerEntry.picker.icon, id)));
|
||||
}
|
||||
|
||||
registerChatContextProvider(id: string, selector: LanguageSelector, provider: IChatContextProvider): void {
|
||||
registerChatContextProvider(id: string, selector: LanguageSelector | undefined, provider: IChatContextProvider): void {
|
||||
const providerEntry = this._providers.get(id) ?? { picker: undefined };
|
||||
providerEntry.chatContextProvider = { selector, provider };
|
||||
this._providers.set(id, providerEntry);
|
||||
@@ -68,6 +69,29 @@ export class ChatContextService extends Disposable {
|
||||
this._registeredPickers.deleteAndDispose(id);
|
||||
}
|
||||
|
||||
updateWorkspaceContextItems(id: string, items: IChatContextItem[]): void {
|
||||
this._workspaceContext.set(id, items);
|
||||
}
|
||||
|
||||
getWorkspaceContextItems(): IChatRequestWorkspaceVariableEntry[] {
|
||||
const items: IChatRequestWorkspaceVariableEntry[] = [];
|
||||
for (const workspaceContexts of this._workspaceContext.values()) {
|
||||
for (const item of workspaceContexts) {
|
||||
if (!item.value) {
|
||||
continue;
|
||||
}
|
||||
items.push({
|
||||
value: item.value,
|
||||
name: item.label,
|
||||
modelDescription: item.modelDescription,
|
||||
id: item.label,
|
||||
kind: 'workspace'
|
||||
});
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
async contextForResource(uri: URI): Promise<StringChatContextValue | undefined> {
|
||||
return this._contextForResource(uri, false);
|
||||
}
|
||||
@@ -75,7 +99,7 @@ export class ChatContextService extends Disposable {
|
||||
private async _contextForResource(uri: URI, withValue: boolean): Promise<StringChatContextValue | undefined> {
|
||||
const scoredProviders: Array<{ score: number; provider: IChatContextProvider }> = [];
|
||||
for (const providerEntry of this._providers.values()) {
|
||||
if (!providerEntry.chatContextProvider?.provider.provideChatContextForResource) {
|
||||
if (!providerEntry.chatContextProvider?.provider.provideChatContextForResource || (providerEntry.chatContextProvider.selector === undefined)) {
|
||||
continue;
|
||||
}
|
||||
const matchScore = score(providerEntry.chatContextProvider.selector, uri, '', true, undefined, undefined);
|
||||
|
||||
@@ -105,6 +105,7 @@ import { ChatRelatedFiles } from './contrib/chatInputRelatedFilesContrib.js';
|
||||
import { resizeImage } from './imageUtils.js';
|
||||
import { IModelPickerDelegate, ModelPickerActionItem } from './modelPicker/modelPickerActionItem.js';
|
||||
import { IModePickerDelegate, ModePickerActionItem } from './modelPicker/modePickerActionItem.js';
|
||||
import { IChatContextService } from './chatContextService.js';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -179,7 +180,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
|
||||
public getAttachedContext(sessionResource: URI) {
|
||||
const contextArr = new ChatRequestVariableSet();
|
||||
contextArr.add(...this.attachmentModel.attachments);
|
||||
contextArr.add(...this.attachmentModel.attachments, ...this.chatContextService.getWorkspaceContextItems());
|
||||
return contextArr;
|
||||
}
|
||||
|
||||
@@ -411,6 +412,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
@ILanguageModelToolsService private readonly toolService: ILanguageModelToolsService,
|
||||
@IChatService private readonly chatService: IChatService,
|
||||
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
|
||||
@IChatContextService private readonly chatContextService: IChatContextService,
|
||||
) {
|
||||
super();
|
||||
this._contextResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility.event }));
|
||||
|
||||
@@ -69,13 +69,22 @@ export function isVendorEntry(entry: IModelItemEntry | IVendorItemEntry): entry
|
||||
return entry.type === 'vendor';
|
||||
}
|
||||
|
||||
export type IViewModelEntry = IModelItemEntry | IVendorItemEntry;
|
||||
|
||||
export interface IViewModelChangeEvent {
|
||||
at: number;
|
||||
removed: number;
|
||||
added: IViewModelEntry[];
|
||||
}
|
||||
|
||||
export class ChatModelsViewModel extends EditorModel {
|
||||
|
||||
private readonly _onDidChangeModelEntries = this._register(new Emitter<void>());
|
||||
readonly onDidChangeModelEntries = this._onDidChangeModelEntries.event;
|
||||
private readonly _onDidChange = this._register(new Emitter<IViewModelChangeEvent>());
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private modelEntries: IModelEntry[];
|
||||
private readonly collapsedVendors = new Set<string>();
|
||||
private searchValue: string = '';
|
||||
|
||||
constructor(
|
||||
@ILanguageModelsService private readonly languageModelsService: ILanguageModelsService,
|
||||
@@ -83,14 +92,21 @@ export class ChatModelsViewModel extends EditorModel {
|
||||
) {
|
||||
super();
|
||||
this.modelEntries = [];
|
||||
|
||||
this._register(this.chatEntitlementService.onDidChangeEntitlement(async () => {
|
||||
await this.resolve();
|
||||
this._onDidChangeModelEntries.fire();
|
||||
}));
|
||||
this._register(this.chatEntitlementService.onDidChangeEntitlement(() => this.refresh()));
|
||||
}
|
||||
|
||||
fetch(searchValue: string): (IModelItemEntry | IVendorItemEntry)[] {
|
||||
private readonly _viewModelEntries: IViewModelEntry[] = [];
|
||||
get viewModelEntries(): readonly IViewModelEntry[] {
|
||||
return this._viewModelEntries;
|
||||
}
|
||||
private splice(at: number, removed: number, added: IViewModelEntry[]): void {
|
||||
this._viewModelEntries.splice(at, removed, ...added);
|
||||
this._onDidChange.fire({ at, removed, added });
|
||||
}
|
||||
|
||||
filter(searchValue: string): readonly IViewModelEntry[] {
|
||||
this.searchValue = searchValue;
|
||||
|
||||
let modelEntries = this.modelEntries;
|
||||
const capabilityMatchesMap = new Map<string, string[]>();
|
||||
|
||||
@@ -135,11 +151,10 @@ export class ChatModelsViewModel extends EditorModel {
|
||||
}
|
||||
|
||||
searchValue = searchValue.trim();
|
||||
if (!searchValue) {
|
||||
return this.toEntries(modelEntries, capabilityMatchesMap);
|
||||
}
|
||||
const filtered = searchValue ? this.filterByText(modelEntries, searchValue, capabilityMatchesMap) : this.toEntries(modelEntries, capabilityMatchesMap);
|
||||
|
||||
return this.filterByText(modelEntries, searchValue, capabilityMatchesMap);
|
||||
this.splice(0, this._viewModelEntries.length, filtered);
|
||||
return this.viewModelEntries;
|
||||
}
|
||||
|
||||
private filterByProviders(modelEntries: IModelEntry[], providers: string[]): IModelEntry[] {
|
||||
@@ -264,8 +279,12 @@ export class ChatModelsViewModel extends EditorModel {
|
||||
}
|
||||
|
||||
override async resolve(): Promise<void> {
|
||||
this.modelEntries = [];
|
||||
await this.refresh();
|
||||
return super.resolve();
|
||||
}
|
||||
|
||||
private async refresh(): Promise<void> {
|
||||
this.modelEntries = [];
|
||||
for (const vendor of this.getVendors()) {
|
||||
const modelIdentifiers = await this.languageModelsService.selectLanguageModels({ vendor: vendor.vendor }, vendor.vendor === 'copilot');
|
||||
const models = coalesce(modelIdentifiers.map(identifier => {
|
||||
@@ -288,12 +307,24 @@ export class ChatModelsViewModel extends EditorModel {
|
||||
}
|
||||
|
||||
this.modelEntries = distinct(this.modelEntries, modelEntry => ChatModelsViewModel.getId(modelEntry));
|
||||
this.filter(this.searchValue);
|
||||
}
|
||||
|
||||
return super.resolve();
|
||||
toggleVisibility(model: IModelItemEntry): void {
|
||||
const isVisible = model.modelEntry.metadata.isUserSelectable ?? false;
|
||||
const newVisibility = !isVisible;
|
||||
this.languageModelsService.updateModelPickerPreference(model.modelEntry.identifier, newVisibility);
|
||||
const metadata = this.languageModelsService.lookupLanguageModel(model.modelEntry.identifier);
|
||||
const index = this.viewModelEntries.indexOf(model);
|
||||
if (metadata) {
|
||||
model.id = ChatModelsViewModel.getId(model.modelEntry);
|
||||
model.modelEntry.metadata = metadata;
|
||||
this.splice(index, 1, [model]);
|
||||
}
|
||||
}
|
||||
|
||||
private static getId(modelEntry: IModelEntry): string {
|
||||
return modelEntry.identifier + modelEntry.vendor + (modelEntry.metadata.version || '');
|
||||
return `${modelEntry.identifier}.${modelEntry.metadata.version}-visible:${modelEntry.metadata.isUserSelectable}`;
|
||||
}
|
||||
|
||||
toggleVendorCollapsed(vendorId: string): void {
|
||||
@@ -302,7 +333,7 @@ export class ChatModelsViewModel extends EditorModel {
|
||||
} else {
|
||||
this.collapsedVendors.add(vendorId);
|
||||
}
|
||||
this._onDidChangeModelEntries.fire();
|
||||
this.filter(this.searchValue);
|
||||
}
|
||||
|
||||
getConfiguredVendors(): IVendorItemEntry[] {
|
||||
|
||||
@@ -288,11 +288,8 @@ class GutterColumnRenderer extends ModelsTableColumnRenderer<IToggleCollapseColu
|
||||
private readonly _onDidToggleCollapse = new Emitter<string>();
|
||||
readonly onDidToggleCollapse = this._onDidToggleCollapse.event;
|
||||
|
||||
private readonly _onDidChange = new Emitter<void>();
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
constructor(
|
||||
@ILanguageModelsService private readonly languageModelsService: ILanguageModelsService
|
||||
private readonly viewModel: ChatModelsViewModel,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -348,11 +345,7 @@ class GutterColumnRenderer extends ModelsTableColumnRenderer<IToggleCollapseColu
|
||||
class: `model-visibility-toggle ${isVisible ? `${ThemeIcon.asClassName(Codicon.eyeClosed)} model-visible` : `${ThemeIcon.asClassName(Codicon.eye)} model-hidden`}`,
|
||||
tooltip: isVisible ? localize('models.visible', 'Hide in the chat model picker') : localize('models.hidden', 'Show in the chat model picker'),
|
||||
checked: !isVisible,
|
||||
run: async () => {
|
||||
const newVisibility = !isVisible;
|
||||
this.languageModelsService.updateModelPickerPreference(modelEntry.identifier, newVisibility);
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
run: async () => this.viewModel.toggleVisibility(entry)
|
||||
});
|
||||
templateData.actionBar.push(toggleVisibilityAction, { icon: true, label: false });
|
||||
}
|
||||
@@ -712,20 +705,13 @@ export class ChatModelsWidget extends Disposable {
|
||||
super();
|
||||
|
||||
this.searchFocusContextKey = CONTEXT_MODELS_SEARCH_FOCUS.bindTo(contextKeyService);
|
||||
this.delayedFiltering = new Delayer<void>(300);
|
||||
this.delayedFiltering = new Delayer<void>(200);
|
||||
this.viewModel = this._register(this.instantiationService.createInstance(ChatModelsViewModel));
|
||||
this.element = DOM.$('.models-widget');
|
||||
this.create(this.element);
|
||||
|
||||
const loadingPromise = this.extensionService.whenInstalledExtensionsRegistered().then(async () => {
|
||||
await this.viewModel.resolve();
|
||||
this.refreshTable();
|
||||
});
|
||||
|
||||
// Show progress indicator while loading models
|
||||
const loadingPromise = this.extensionService.whenInstalledExtensionsRegistered().then(async () => this.viewModel.resolve());
|
||||
this.editorProgressService.showWhile(loadingPromise, 300);
|
||||
|
||||
this._register(this.viewModel.onDidChangeModelEntries(() => this.refreshTable()));
|
||||
}
|
||||
|
||||
private create(container: HTMLElement): void {
|
||||
@@ -765,7 +751,6 @@ export class ChatModelsWidget extends Disposable {
|
||||
focusContextKey: this.searchFocusContextKey,
|
||||
},
|
||||
));
|
||||
this._register(this.searchWidget.onInputDidChange(() => this.filterModels()));
|
||||
|
||||
const filterAction = this._register(new ModelsFilterAction());
|
||||
const clearSearchAction = this._register(new Action(
|
||||
@@ -781,6 +766,7 @@ export class ChatModelsWidget extends Disposable {
|
||||
|
||||
this._register(this.searchWidget.onInputDidChange(() => {
|
||||
clearSearchAction.enabled = !!this.searchWidget.getValue();
|
||||
this.filterModels();
|
||||
}));
|
||||
|
||||
this.searchActionsContainer = DOM.append(searchContainer, $('.models-search-actions'));
|
||||
@@ -821,7 +807,7 @@ export class ChatModelsWidget extends Disposable {
|
||||
this.tableContainer = DOM.append(container, $('.models-table-container'));
|
||||
|
||||
// Create table
|
||||
const gutterColumnRenderer = this.instantiationService.createInstance(GutterColumnRenderer);
|
||||
const gutterColumnRenderer = this.instantiationService.createInstance(GutterColumnRenderer, this.viewModel);
|
||||
const modelNameColumnRenderer = this.instantiationService.createInstance(ModelNameColumnRenderer);
|
||||
const costColumnRenderer = this.instantiationService.createInstance(MultiplierColumnRenderer);
|
||||
const tokenLimitsColumnRenderer = this.instantiationService.createInstance(TokenLimitsColumnRenderer);
|
||||
@@ -832,12 +818,6 @@ export class ChatModelsWidget extends Disposable {
|
||||
this.viewModel.toggleVendorCollapsed(vendorId);
|
||||
}));
|
||||
|
||||
this._register(gutterColumnRenderer.onDidChange(e => {
|
||||
this.viewModel.resolve().then(() => {
|
||||
this.refreshTable();
|
||||
});
|
||||
}));
|
||||
|
||||
this._register(actionsColumnRenderer.onDidChange(e => {
|
||||
this.viewModel.resolve().then(() => {
|
||||
this.refreshTable();
|
||||
@@ -960,6 +940,8 @@ export class ChatModelsWidget extends Disposable {
|
||||
}
|
||||
}));
|
||||
|
||||
this.table.splice(0, this.table.length, this.viewModel.viewModelEntries);
|
||||
this._register(this.viewModel.onDidChange(({ at, removed, added }) => this.table.splice(at, removed, added)));
|
||||
}
|
||||
|
||||
private filterModels(): void {
|
||||
@@ -968,7 +950,7 @@ export class ChatModelsWidget extends Disposable {
|
||||
|
||||
private async refreshTable(): Promise<void> {
|
||||
const searchValue = this.searchWidget.getValue();
|
||||
const modelItems = this.viewModel.fetch(searchValue);
|
||||
const modelItems = this.viewModel.filter(searchValue);
|
||||
|
||||
const vendors = this.viewModel.getVendors();
|
||||
const configuredVendors = new Set(this.viewModel.getConfiguredVendors().map(cv => cv.vendorEntry.vendor));
|
||||
|
||||
@@ -662,7 +662,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
|
||||
});
|
||||
}
|
||||
|
||||
async hasChatSessionItemProvider(chatViewType: string): Promise<boolean> {
|
||||
async activateChatSessionItemProvider(chatViewType: string): Promise<IChatSessionItemProvider | undefined> {
|
||||
await this._extensionService.whenInstalledExtensionsRegistered();
|
||||
const resolvedType = this._resolveToPrimaryType(chatViewType);
|
||||
if (resolvedType) {
|
||||
@@ -671,16 +671,16 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
|
||||
|
||||
const contribution = this._contributions.get(chatViewType)?.contribution;
|
||||
if (contribution && !this._isContributionAvailable(contribution)) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this._itemsProviders.has(chatViewType)) {
|
||||
return true;
|
||||
return this._itemsProviders.get(chatViewType);
|
||||
}
|
||||
|
||||
await this._extensionService.activateByEvent(`onChatSession:${chatViewType}`);
|
||||
|
||||
return this._itemsProviders.has(chatViewType);
|
||||
return this._itemsProviders.get(chatViewType);
|
||||
}
|
||||
|
||||
async canResolveChatSession(chatSessionResource: URI) {
|
||||
@@ -709,7 +709,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
|
||||
}
|
||||
|
||||
private async getChatSessionItems(chatSessionType: string, token: CancellationToken): Promise<IChatSessionItem[]> {
|
||||
if (!(await this.hasChatSessionItemProvider(chatSessionType))) {
|
||||
if (!(await this.activateChatSessionItemProvider(chatSessionType))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -790,7 +790,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
|
||||
request: IChatAgentRequest;
|
||||
metadata?: any;
|
||||
}, token: CancellationToken): Promise<IChatSessionItem> {
|
||||
if (!(await this.hasChatSessionItemProvider(chatSessionType))) {
|
||||
if (!(await this.activateChatSessionItemProvider(chatSessionType))) {
|
||||
throw Error(`Cannot find provider for ${chatSessionType}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,22 +7,19 @@ import { Codicon } from '../../../../../base/common/codicons.js';
|
||||
import { Emitter, Event } from '../../../../../base/common/event.js';
|
||||
import { Disposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { ResourceSet } from '../../../../../base/common/map.js';
|
||||
import { Schemas } from '../../../../../base/common/network.js';
|
||||
import { IObservable } from '../../../../../base/common/observable.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { IWorkbenchContribution } from '../../../../common/contributions.js';
|
||||
import { ModifiedFileEntryState } from '../../common/chatEditingService.js';
|
||||
import { IChatModel } from '../../common/chatModel.js';
|
||||
import { IChatService } from '../../common/chatService.js';
|
||||
import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js';
|
||||
import { ChatAgentLocation } from '../../common/constants.js';
|
||||
import { IChatWidget, IChatWidgetService } from '../chat.js';
|
||||
import { IChatWidget, IChatWidgetService, isIChatViewViewContext } from '../chat.js';
|
||||
import { ChatSessionItemWithProvider } from './common.js';
|
||||
|
||||
export class LocalChatSessionsProvider extends Disposable implements IChatSessionItemProvider, IWorkbenchContribution {
|
||||
static readonly ID = 'workbench.contrib.localChatSessionsProvider';
|
||||
static readonly CHAT_WIDGET_VIEW_ID = 'workbench.panel.chat.view.copilot';
|
||||
static readonly CHAT_WIDGET_VIEW_RESOURCE = URI.parse(`${Schemas.vscodeLocalChatSession}://widget`);
|
||||
readonly chatSessionType = localChatSessionType;
|
||||
|
||||
private readonly _onDidChange = this._register(new Emitter<void>());
|
||||
@@ -59,8 +56,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio
|
||||
this._register(this.chatWidgetService.onDidAddWidget(widget => {
|
||||
// Only fire for chat view instance
|
||||
if (widget.location === ChatAgentLocation.Chat &&
|
||||
typeof widget.viewContext === 'object' &&
|
||||
'viewId' in widget.viewContext &&
|
||||
isIChatViewViewContext(widget.viewContext) &&
|
||||
widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID) {
|
||||
this._onDidChange.fire();
|
||||
this._registerWidgetModelListeners(widget);
|
||||
@@ -69,7 +65,7 @@ export class LocalChatSessionsProvider extends Disposable implements IChatSessio
|
||||
|
||||
// Check for existing chat widgets and register listeners
|
||||
const existingWidgets = this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Chat)
|
||||
.filter(widget => typeof widget.viewContext === 'object' && 'viewId' in widget.viewContext && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID);
|
||||
.filter(widget => isIChatViewViewContext(widget.viewContext) && widget.viewContext.viewId === LocalChatSessionsProvider.CHAT_WIDGET_VIEW_ID);
|
||||
|
||||
existingWidgets.forEach(widget => {
|
||||
this._registerWidgetModelListeners(widget);
|
||||
|
||||
@@ -86,7 +86,7 @@ export class ChatSessionsViewContrib extends Disposable implements IWorkbenchCon
|
||||
private async updateViewRegistration(): Promise<void> {
|
||||
// prepare all chat session providers
|
||||
const contributions = this.chatSessionsService.getAllChatSessionContributions();
|
||||
await Promise.all(contributions.map(contrib => this.chatSessionsService.hasChatSessionItemProvider(contrib.type)));
|
||||
await Promise.all(contributions.map(contrib => this.chatSessionsService.activateChatSessionItemProvider(contrib.type)));
|
||||
const currentProviders = this.getAllChatSessionItemProviders();
|
||||
const currentProviderIds = new Set(currentProviders.map(p => p.chatSessionType));
|
||||
|
||||
|
||||
@@ -10,11 +10,9 @@ import { IActionViewItem } from '../../../../../../base/browser/ui/actionbar/act
|
||||
import { IBaseActionViewItemOptions } from '../../../../../../base/browser/ui/actionbar/actionViewItems.js';
|
||||
import { ITreeContextMenuEvent } from '../../../../../../base/browser/ui/tree/tree.js';
|
||||
import { IAction, toAction } from '../../../../../../base/common/actions.js';
|
||||
import { coalesce } from '../../../../../../base/common/arrays.js';
|
||||
import { Codicon } from '../../../../../../base/common/codicons.js';
|
||||
import { FuzzyScore } from '../../../../../../base/common/filters.js';
|
||||
import { MarshalledId } from '../../../../../../base/common/marshallingIds.js';
|
||||
import { isEqual } from '../../../../../../base/common/resources.js';
|
||||
import { truncate } from '../../../../../../base/common/strings.js';
|
||||
import { URI } from '../../../../../../base/common/uri.js';
|
||||
import * as nls from '../../../../../../nls.js';
|
||||
@@ -304,11 +302,7 @@ export class SessionsViewPane extends ViewPane {
|
||||
const renderer = this.instantiationService.createInstance(SessionsRenderer, this.viewDescriptorService.getViewLocationById(this.viewId));
|
||||
this._register(renderer);
|
||||
|
||||
const getResourceForElement = (element: ChatSessionItemWithProvider): URI | null => {
|
||||
if (isEqual(element.resource, LocalChatSessionsProvider.CHAT_WIDGET_VIEW_RESOURCE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getResourceForElement = (element: ChatSessionItemWithProvider): URI => {
|
||||
return element.resource;
|
||||
};
|
||||
|
||||
@@ -324,14 +318,14 @@ export class SessionsViewPane extends ViewPane {
|
||||
onDragStart: (data, originalEvent) => {
|
||||
try {
|
||||
const elements = data.getData() as ChatSessionItemWithProvider[];
|
||||
const uris = coalesce(elements.map(getResourceForElement));
|
||||
const uris = elements.map(getResourceForElement);
|
||||
this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, uris, originalEvent));
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
},
|
||||
getDragURI: (element: ChatSessionItemWithProvider) => {
|
||||
return getResourceForElement(element)?.toString() ?? null;
|
||||
return getResourceForElement(element).toString();
|
||||
},
|
||||
getDragLabel: (elements: ChatSessionItemWithProvider[]) => {
|
||||
if (elements.length === 1) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { $, getWindow } from '../../../../base/browser/dom.js';
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import { MarshalledId } from '../../../../base/common/marshallingIds.js';
|
||||
import { autorun } from '../../../../base/common/observable.js';
|
||||
import { autorun, IReader } from '../../../../base/common/observable.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
@@ -229,14 +229,14 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
|
||||
}));
|
||||
this._widget.render(parent);
|
||||
|
||||
const updateWidgetVisibility = () => {
|
||||
this._widget.setVisible(this.isBodyVisible() && !welcomeController.isShowingWelcome.get());
|
||||
const updateWidgetVisibility = (r?: IReader) => {
|
||||
this._widget.setVisible(this.isBodyVisible() && !welcomeController.isShowingWelcome.read(r));
|
||||
};
|
||||
this._register(this.onDidChangeBodyVisibility(() => {
|
||||
updateWidgetVisibility();
|
||||
}));
|
||||
this._register(autorun(r => {
|
||||
updateWidgetVisibility();
|
||||
updateWidgetVisibility(r);
|
||||
}));
|
||||
|
||||
const info = this.getTransferredOrPersistedSessionInfo();
|
||||
|
||||
@@ -17,7 +17,6 @@ import { ChatAgentLocation } from '../common/constants.js';
|
||||
import { ChatViewId, ChatViewPaneTarget, IChatWidget, IChatWidgetService, IQuickChatService, isIChatViewViewContext } from './chat.js';
|
||||
import { ChatEditor, IChatEditorOptions } from './chatEditor.js';
|
||||
import { findExistingChatEditorByUri } from './chatSessions/common.js';
|
||||
import { LocalChatSessionsProvider } from './chatSessions/localChatSessionsProvider.js';
|
||||
import { ChatViewPane } from './chatViewPane.js';
|
||||
|
||||
export class ChatWidgetService extends Disposable implements IChatWidgetService {
|
||||
@@ -95,15 +94,6 @@ export class ChatWidgetService extends Disposable implements IChatWidgetService
|
||||
openSession(sessionResource: URI, target?: typeof ChatViewPaneTarget): Promise<IChatWidget | undefined>;
|
||||
openSession(sessionResource: URI, target?: PreferredGroup, options?: IChatEditorOptions): Promise<IChatWidget | undefined>;
|
||||
async openSession(sessionResource: URI, target?: typeof ChatViewPaneTarget | PreferredGroup, options?: IChatEditorOptions): Promise<IChatWidget | undefined> {
|
||||
// TODO remove this, open the real resource
|
||||
if (isEqual(sessionResource, LocalChatSessionsProvider.CHAT_WIDGET_VIEW_RESOURCE)) {
|
||||
const chatViewPane = await this.viewsService.openView<ChatViewPane>(ChatViewId, true);
|
||||
if (chatViewPane) {
|
||||
chatViewPane.focusInput();
|
||||
}
|
||||
return chatViewPane?.widget;
|
||||
}
|
||||
|
||||
const alreadyOpenWidget = await this.revealSessionIfAlreadyOpen(sessionResource);
|
||||
if (alreadyOpenWidget) {
|
||||
return alreadyOpenWidget;
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
font-size: var(--vscode-chat-font-size-body-s);
|
||||
color: var(--vscode-descriptionForeground);
|
||||
overflow: hidden;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.interactive-item-container .detail-container .detail .agentOrSlashCommandDetected A {
|
||||
@@ -2494,6 +2493,10 @@ have to be updated for changes to the rules above, or to support more deeply nes
|
||||
display: none;
|
||||
}
|
||||
|
||||
.interactive-request .header.partially-disabled .detail-container {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.interactive-item-container .header .detail .codicon-check {
|
||||
margin-right: 7px;
|
||||
vertical-align: middle;
|
||||
|
||||
@@ -153,13 +153,6 @@ export interface IChatAgentRequest {
|
||||
editedFileEvents?: IChatAgentEditedFileEvent[];
|
||||
isSubagent?: boolean;
|
||||
|
||||
/**
|
||||
* Summary data for chat sessions context
|
||||
*/
|
||||
chatSummary?: {
|
||||
prompt?: string;
|
||||
history?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IChatQuestion {
|
||||
|
||||
@@ -932,13 +932,6 @@ export interface IChatSendRequestOptions {
|
||||
*/
|
||||
confirmation?: string;
|
||||
|
||||
/**
|
||||
* Summary data for chat sessions context
|
||||
*/
|
||||
chatSummary?: {
|
||||
prompt?: string;
|
||||
history?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const IChatService = createDecorator<IChatService>('IChatService');
|
||||
|
||||
@@ -404,7 +404,7 @@ export class ChatService extends Disposable implements IChatService {
|
||||
});
|
||||
}
|
||||
|
||||
shouldBeInHistory(entry: Partial<ChatModel>) {
|
||||
private shouldBeInHistory(entry: Partial<ChatModel>) {
|
||||
if (entry.sessionResource) {
|
||||
return !entry.isImported && LocalChatSessionUri.parseLocalSessionId(entry.sessionResource) && entry.initialLocation !== ChatAgentLocation.EditorInline;
|
||||
}
|
||||
@@ -908,7 +908,6 @@ export class ChatService extends Disposable implements IChatService {
|
||||
userSelectedTools: options?.userSelectedTools?.get(),
|
||||
modeInstructions: options?.modeInfo?.modeInstructions,
|
||||
editedFileEvents: request.editedFileEvents,
|
||||
chatSummary: options?.chatSummary
|
||||
};
|
||||
|
||||
let isInitialTools = true;
|
||||
@@ -937,6 +936,7 @@ export class ChatService extends Disposable implements IChatService {
|
||||
!commandPart &&
|
||||
!agentSlashCommandPart &&
|
||||
enableCommandDetection &&
|
||||
location !== ChatAgentLocation.EditorInline &&
|
||||
options?.modeInfo?.kind !== ChatModeKind.Agent &&
|
||||
options?.modeInfo?.kind !== ChatModeKind.Edit &&
|
||||
!options?.agentIdSilent
|
||||
|
||||
@@ -155,7 +155,7 @@ export interface IChatSessionsService {
|
||||
readonly onDidChangeInProgress: Event<void>;
|
||||
|
||||
registerChatSessionItemProvider(provider: IChatSessionItemProvider): IDisposable;
|
||||
hasChatSessionItemProvider(chatSessionType: string): Promise<boolean>;
|
||||
activateChatSessionItemProvider(chatSessionType: string): Promise<IChatSessionItemProvider | undefined>;
|
||||
getAllChatSessionItemProviders(): IChatSessionItemProvider[];
|
||||
|
||||
getAllChatSessionContributions(): IChatSessionsExtensionPoint[];
|
||||
|
||||
@@ -90,6 +90,13 @@ export interface IChatRequestStringVariableEntry extends IBaseChatRequestVariabl
|
||||
readonly uri: URI;
|
||||
}
|
||||
|
||||
export interface IChatRequestWorkspaceVariableEntry extends IBaseChatRequestVariableEntry {
|
||||
readonly kind: 'workspace';
|
||||
readonly value: string;
|
||||
readonly modelDescription?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IChatRequestPasteVariableEntry extends IBaseChatRequestVariableEntry {
|
||||
readonly kind: 'paste';
|
||||
readonly code: string;
|
||||
@@ -260,7 +267,7 @@ export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChat
|
||||
| IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry
|
||||
| IPromptFileVariableEntry | IPromptTextVariableEntry
|
||||
| ISCMHistoryItemVariableEntry | ISCMHistoryItemChangeVariableEntry | ISCMHistoryItemChangeRangeVariableEntry | ITerminalVariableEntry
|
||||
| IChatRequestStringVariableEntry;
|
||||
| IChatRequestStringVariableEntry | IChatRequestWorkspaceVariableEntry;
|
||||
|
||||
export namespace IChatRequestVariableEntry {
|
||||
|
||||
@@ -293,6 +300,10 @@ export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is ICh
|
||||
return obj.kind === 'paste';
|
||||
}
|
||||
|
||||
export function isWorkspaceVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestWorkspaceVariableEntry {
|
||||
return obj.kind === 'workspace';
|
||||
}
|
||||
|
||||
export function isImageVariableEntry(obj: IChatRequestVariableEntry): obj is IImageVariableEntry {
|
||||
return obj.kind === 'image';
|
||||
}
|
||||
|
||||
+1
-4
@@ -16,7 +16,7 @@ import { ChatModeKind } from '../../constants.js';
|
||||
import { ILanguageModelChatMetadata, ILanguageModelsService } from '../../languageModels.js';
|
||||
import { ILanguageModelToolsService } from '../../languageModelToolsService.js';
|
||||
import { getPromptsTypeForLanguageId, PromptsType } from '../promptTypes.js';
|
||||
import { GithubPromptHeaderAttributes, IArrayValue, IHeaderAttribute, IStringValue, ParsedPromptFile, PROMPT_NAME_REGEXP, PromptHeaderAttributes, Target } from '../promptFileParser.js';
|
||||
import { GithubPromptHeaderAttributes, IArrayValue, IHeaderAttribute, IStringValue, ParsedPromptFile, PromptHeaderAttributes, Target } from '../promptFileParser.js';
|
||||
import { Disposable, DisposableStore, toDisposable } from '../../../../../../base/common/lifecycle.js';
|
||||
import { Delayer } from '../../../../../../base/common/async.js';
|
||||
import { ResourceMap } from '../../../../../../base/common/map.js';
|
||||
@@ -197,9 +197,6 @@ export class PromptValidator {
|
||||
report(toMarker(localize('promptValidator.nameShouldNotBeEmpty', "The 'name' attribute must not be empty."), nameAttribute.value.range, MarkerSeverity.Error));
|
||||
return;
|
||||
}
|
||||
if (!PROMPT_NAME_REGEXP.test(nameAttribute.value.value)) {
|
||||
report(toMarker(localize('promptValidator.nameInvalidCharacters', "The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods."), nameAttribute.value.range, MarkerSeverity.Error));
|
||||
}
|
||||
}
|
||||
|
||||
private validateDescription(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): void {
|
||||
|
||||
@@ -10,8 +10,6 @@ import { URI } from '../../../../../base/common/uri.js';
|
||||
import { parse, YamlNode, YamlParseError, Position as YamlPosition } from '../../../../../base/common/yaml.js';
|
||||
import { Range } from '../../../../../editor/common/core/range.js';
|
||||
|
||||
export const PROMPT_NAME_REGEXP = /^[\p{L}\d_\-\.]+$/u;
|
||||
|
||||
export class PromptFileParser {
|
||||
constructor() {
|
||||
}
|
||||
@@ -162,11 +160,7 @@ export class PromptHeader {
|
||||
}
|
||||
|
||||
public get name(): string | undefined {
|
||||
const name = this.getStringAttribute(PromptHeaderAttributes.name);
|
||||
if (name && PROMPT_NAME_REGEXP.test(name)) {
|
||||
return name;
|
||||
}
|
||||
return undefined;
|
||||
return this.getStringAttribute(PromptHeaderAttributes.name);
|
||||
}
|
||||
|
||||
public get description(): string | undefined {
|
||||
|
||||
@@ -246,8 +246,10 @@ export class PromptsService extends Disposable implements IPromptsService {
|
||||
}
|
||||
|
||||
private asChatPromptSlashCommand(parsedPromptFile: ParsedPromptFile, promptPath: IPromptPath): IChatPromptSlashCommand {
|
||||
let name = parsedPromptFile?.header?.name ?? promptPath.name ?? getCleanPromptName(promptPath.uri);
|
||||
name = name.replace(/[^\p{L}\d_\-\.]+/gu, '-'); // replace spaces with dashes
|
||||
return {
|
||||
name: parsedPromptFile?.header?.name ?? promptPath.name ?? getCleanPromptName(promptPath.uri),
|
||||
name: name,
|
||||
description: parsedPromptFile?.header?.description ?? promptPath.description,
|
||||
argumentHint: parsedPromptFile?.header?.argumentHint,
|
||||
parsedPromptFile,
|
||||
@@ -359,7 +361,7 @@ export class PromptsService extends Disposable implements IPromptsService {
|
||||
bucket.set(uri, entryPromise);
|
||||
|
||||
const flushCachesIfRequired = () => {
|
||||
this.cachedFileLocations[PromptsType.agent] = undefined;
|
||||
this.cachedFileLocations[type] = undefined;
|
||||
switch (type) {
|
||||
case PromptsType.agent:
|
||||
this.cachedCustomAgents.refresh();
|
||||
|
||||
@@ -419,8 +419,7 @@ suite('AgentSessionsViewModel', () => {
|
||||
await viewModel.resolve(undefined);
|
||||
|
||||
assert.strictEqual(viewModel.sessions.length, 1);
|
||||
assert.strictEqual(viewModel.sessions[0].provider, provider);
|
||||
assert.strictEqual(viewModel.sessions[0].provider.chatSessionType, 'test-type');
|
||||
assert.strictEqual(viewModel.sessions[0].providerType, 'test-type');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -536,7 +535,7 @@ suite('AgentSessionsViewModel', () => {
|
||||
await viewModel.resolve(undefined);
|
||||
|
||||
assert.strictEqual(viewModel.sessions.length, 1);
|
||||
assert.strictEqual(viewModel.sessions[0].provider.chatSessionType, localChatSessionType);
|
||||
assert.strictEqual(viewModel.sessions[0].providerType, localChatSessionType);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -725,11 +724,7 @@ suite('AgentSessionsViewModel - Helper Functions', () => {
|
||||
|
||||
test('isLocalAgentSessionItem should identify local sessions', () => {
|
||||
const localSession: IAgentSessionViewModel = {
|
||||
provider: {
|
||||
chatSessionType: localChatSessionType,
|
||||
onDidChangeChatSessionItems: Event.None,
|
||||
provideChatSessionItems: async () => []
|
||||
},
|
||||
providerType: localChatSessionType,
|
||||
providerLabel: 'Local',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://local-1'),
|
||||
@@ -741,12 +736,8 @@ suite('AgentSessionsViewModel - Helper Functions', () => {
|
||||
};
|
||||
|
||||
const remoteSession: IAgentSessionViewModel = {
|
||||
provider: {
|
||||
chatSessionType: 'remote',
|
||||
onDidChangeChatSessionItems: Event.None,
|
||||
provideChatSessionItems: async () => []
|
||||
},
|
||||
providerLabel: 'Local',
|
||||
providerType: 'remote',
|
||||
providerLabel: 'Remote',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://remote-1'),
|
||||
label: 'Remote',
|
||||
@@ -762,11 +753,7 @@ suite('AgentSessionsViewModel - Helper Functions', () => {
|
||||
|
||||
test('isAgentSession should identify session view models', () => {
|
||||
const session: IAgentSessionViewModel = {
|
||||
provider: {
|
||||
chatSessionType: 'test',
|
||||
onDidChangeChatSessionItems: Event.None,
|
||||
provideChatSessionItems: async () => []
|
||||
},
|
||||
providerType: 'test',
|
||||
providerLabel: 'Local',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://test-1'),
|
||||
@@ -787,11 +774,7 @@ suite('AgentSessionsViewModel - Helper Functions', () => {
|
||||
|
||||
test('isAgentSessionsViewModel should identify sessions view models', () => {
|
||||
const session: IAgentSessionViewModel = {
|
||||
provider: {
|
||||
chatSessionType: 'test',
|
||||
onDidChangeChatSessionItems: Event.None,
|
||||
provideChatSessionItems: async () => []
|
||||
},
|
||||
providerType: 'test',
|
||||
providerLabel: 'Local',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://test-1'),
|
||||
@@ -855,7 +838,7 @@ suite('AgentSessionsViewFilter', () => {
|
||||
};
|
||||
|
||||
const session1: IAgentSessionViewModel = {
|
||||
provider: provider1,
|
||||
providerType: provider1.chatSessionType,
|
||||
providerLabel: 'Provider 1',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://session-1'),
|
||||
@@ -866,7 +849,7 @@ suite('AgentSessionsViewFilter', () => {
|
||||
};
|
||||
|
||||
const session2: IAgentSessionViewModel = {
|
||||
provider: provider2,
|
||||
providerType: provider2.chatSessionType,
|
||||
providerLabel: 'Provider 2',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://session-2'),
|
||||
@@ -907,7 +890,7 @@ suite('AgentSessionsViewFilter', () => {
|
||||
};
|
||||
|
||||
const archivedSession: IAgentSessionViewModel = {
|
||||
provider,
|
||||
providerType: provider.chatSessionType,
|
||||
providerLabel: 'Test Provider',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://archived-session'),
|
||||
@@ -918,7 +901,7 @@ suite('AgentSessionsViewFilter', () => {
|
||||
};
|
||||
|
||||
const activeSession: IAgentSessionViewModel = {
|
||||
provider,
|
||||
providerType: provider.chatSessionType,
|
||||
providerLabel: 'Test Provider',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://active-session'),
|
||||
@@ -959,7 +942,7 @@ suite('AgentSessionsViewFilter', () => {
|
||||
};
|
||||
|
||||
const failedSession: IAgentSessionViewModel = {
|
||||
provider,
|
||||
providerType: provider.chatSessionType,
|
||||
providerLabel: 'Test Provider',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://failed-session'),
|
||||
@@ -970,7 +953,7 @@ suite('AgentSessionsViewFilter', () => {
|
||||
};
|
||||
|
||||
const completedSession: IAgentSessionViewModel = {
|
||||
provider,
|
||||
providerType: provider.chatSessionType,
|
||||
providerLabel: 'Test Provider',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://completed-session'),
|
||||
@@ -981,7 +964,7 @@ suite('AgentSessionsViewFilter', () => {
|
||||
};
|
||||
|
||||
const inProgressSession: IAgentSessionViewModel = {
|
||||
provider,
|
||||
providerType: provider.chatSessionType,
|
||||
providerLabel: 'Test Provider',
|
||||
icon: Codicon.chatSparkle,
|
||||
resource: URI.parse('test://inprogress-session'),
|
||||
|
||||
@@ -250,7 +250,7 @@ suite('ChatModelsViewModel', () => {
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
test('should fetch all models without filters', () => {
|
||||
const results = viewModel.fetch('');
|
||||
const results = viewModel.filter('');
|
||||
|
||||
// Should have 2 vendor entries and 4 model entries (grouped by vendor)
|
||||
assert.strictEqual(results.length, 6);
|
||||
@@ -263,7 +263,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by provider name', () => {
|
||||
const results = viewModel.fetch('@provider:copilot');
|
||||
const results = viewModel.filter('@provider:copilot');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 2);
|
||||
@@ -271,7 +271,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by provider display name', () => {
|
||||
const results = viewModel.fetch('@provider:OpenAI');
|
||||
const results = viewModel.filter('@provider:OpenAI');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 2);
|
||||
@@ -279,14 +279,14 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by multiple providers with OR logic', () => {
|
||||
const results = viewModel.fetch('@provider:copilot @provider:openai');
|
||||
const results = viewModel.filter('@provider:copilot @provider:openai');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 4);
|
||||
});
|
||||
|
||||
test('should filter by single capability - tools', () => {
|
||||
const results = viewModel.fetch('@capability:tools');
|
||||
const results = viewModel.filter('@capability:tools');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 3);
|
||||
@@ -294,7 +294,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by single capability - vision', () => {
|
||||
const results = viewModel.fetch('@capability:vision');
|
||||
const results = viewModel.filter('@capability:vision');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 3);
|
||||
@@ -302,7 +302,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by single capability - agent', () => {
|
||||
const results = viewModel.fetch('@capability:agent');
|
||||
const results = viewModel.filter('@capability:agent');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 1);
|
||||
@@ -310,7 +310,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by multiple capabilities with AND logic', () => {
|
||||
const results = viewModel.fetch('@capability:tools @capability:vision');
|
||||
const results = viewModel.filter('@capability:tools @capability:vision');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
// Should only return models that have BOTH tools and vision
|
||||
@@ -322,7 +322,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by three capabilities with AND logic', () => {
|
||||
const results = viewModel.fetch('@capability:tools @capability:vision @capability:agent');
|
||||
const results = viewModel.filter('@capability:tools @capability:vision @capability:agent');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
// Should only return gpt-4o which has all three
|
||||
@@ -331,7 +331,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should return no results when filtering by incompatible capabilities', () => {
|
||||
const results = viewModel.fetch('@capability:vision @capability:agent');
|
||||
const results = viewModel.filter('@capability:vision @capability:agent');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
// Only gpt-4o has both vision and agent, but gpt-4-vision doesn't have agent
|
||||
@@ -340,7 +340,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by visibility - visible:true', () => {
|
||||
const results = viewModel.fetch('@visible:true');
|
||||
const results = viewModel.filter('@visible:true');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 3);
|
||||
@@ -348,7 +348,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by visibility - visible:false', () => {
|
||||
const results = viewModel.fetch('@visible:false');
|
||||
const results = viewModel.filter('@visible:false');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 1);
|
||||
@@ -356,7 +356,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should combine provider and capability filters', () => {
|
||||
const results = viewModel.fetch('@provider:copilot @capability:vision');
|
||||
const results = viewModel.filter('@provider:copilot @capability:vision');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 2);
|
||||
@@ -367,7 +367,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should combine provider, capability, and visibility filters', () => {
|
||||
const results = viewModel.fetch('@provider:openai @capability:vision @visible:false');
|
||||
const results = viewModel.filter('@provider:openai @capability:vision @visible:false');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 1);
|
||||
@@ -375,7 +375,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by text matching model name', () => {
|
||||
const results = viewModel.fetch('GPT-4o');
|
||||
const results = viewModel.filter('GPT-4o');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 1);
|
||||
@@ -384,7 +384,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should filter by text matching vendor name', () => {
|
||||
const results = viewModel.fetch('GitHub');
|
||||
const results = viewModel.filter('GitHub');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.strictEqual(models.length, 2);
|
||||
@@ -392,7 +392,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should combine text search with capability filter', () => {
|
||||
const results = viewModel.fetch('@capability:tools GPT');
|
||||
const results = viewModel.filter('@capability:tools GPT');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
// Should match all models with tools capability and 'GPT' in name
|
||||
@@ -401,21 +401,21 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should handle empty search value', () => {
|
||||
const results = viewModel.fetch('');
|
||||
const results = viewModel.filter('');
|
||||
|
||||
// Should return all models grouped by vendor
|
||||
assert.ok(results.length > 0);
|
||||
});
|
||||
|
||||
test('should handle search value with only whitespace', () => {
|
||||
const results = viewModel.fetch(' ');
|
||||
const results = viewModel.filter(' ');
|
||||
|
||||
// Should return all models grouped by vendor
|
||||
assert.ok(results.length > 0);
|
||||
});
|
||||
|
||||
test('should match capability text in free text search', () => {
|
||||
const results = viewModel.fetch('vision');
|
||||
const results = viewModel.filter('vision');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
// Should match models that have vision capability or "vision" in their name
|
||||
@@ -429,7 +429,7 @@ suite('ChatModelsViewModel', () => {
|
||||
test('should toggle vendor collapsed state', () => {
|
||||
viewModel.toggleVendorCollapsed('copilot');
|
||||
|
||||
const results = viewModel.fetch('');
|
||||
const results = viewModel.filter('');
|
||||
const copilotVendor = results.find(r => isVendorEntry(r) && (r as IVendorItemEntry).vendorEntry.vendor === 'copilot') as IVendorItemEntry;
|
||||
|
||||
assert.ok(copilotVendor);
|
||||
@@ -443,7 +443,7 @@ suite('ChatModelsViewModel', () => {
|
||||
|
||||
// Toggle back
|
||||
viewModel.toggleVendorCollapsed('copilot');
|
||||
const resultsAfterExpand = viewModel.fetch('');
|
||||
const resultsAfterExpand = viewModel.filter('');
|
||||
const copilotModelsAfterExpand = resultsAfterExpand.filter(r =>
|
||||
!isVendorEntry(r) && (r as IModelItemEntry).modelEntry.vendor === 'copilot'
|
||||
);
|
||||
@@ -452,7 +452,7 @@ suite('ChatModelsViewModel', () => {
|
||||
|
||||
test('should fire onDidChangeModelEntries when entitlement changes', async () => {
|
||||
let fired = false;
|
||||
store.add(viewModel.onDidChangeModelEntries(() => {
|
||||
store.add(viewModel.onDidChange(() => {
|
||||
fired = true;
|
||||
}));
|
||||
|
||||
@@ -468,7 +468,7 @@ suite('ChatModelsViewModel', () => {
|
||||
// When a search string is fully quoted (starts and ends with quotes),
|
||||
// the completeMatch flag is set to true, which currently skips all matching
|
||||
// This test verifies the quotes are processed without errors
|
||||
const results = viewModel.fetch('"GPT"');
|
||||
const results = viewModel.filter('"GPT"');
|
||||
|
||||
// The function should complete without error
|
||||
// Note: complete match logic (both quotes) currently doesn't perform matching
|
||||
@@ -476,7 +476,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should remove filter keywords from text search', () => {
|
||||
const results = viewModel.fetch('@provider:copilot @capability:vision GPT');
|
||||
const results = viewModel.filter('@provider:copilot @capability:vision GPT');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
// Should only search 'GPT' in model names, not the filter keywords
|
||||
@@ -485,9 +485,9 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should handle case-insensitive capability matching', () => {
|
||||
const results1 = viewModel.fetch('@capability:TOOLS');
|
||||
const results2 = viewModel.fetch('@capability:tools');
|
||||
const results3 = viewModel.fetch('@capability:Tools');
|
||||
const results1 = viewModel.filter('@capability:TOOLS');
|
||||
const results2 = viewModel.filter('@capability:tools');
|
||||
const results3 = viewModel.filter('@capability:Tools');
|
||||
|
||||
const models1 = results1.filter(r => !isVendorEntry(r));
|
||||
const models2 = results2.filter(r => !isVendorEntry(r));
|
||||
@@ -498,8 +498,8 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should support toolcalling alias for tools capability', () => {
|
||||
const resultsTools = viewModel.fetch('@capability:tools');
|
||||
const resultsToolCalling = viewModel.fetch('@capability:toolcalling');
|
||||
const resultsTools = viewModel.filter('@capability:tools');
|
||||
const resultsToolCalling = viewModel.filter('@capability:toolcalling');
|
||||
|
||||
const modelsTools = resultsTools.filter(r => !isVendorEntry(r));
|
||||
const modelsToolCalling = resultsToolCalling.filter(r => !isVendorEntry(r));
|
||||
@@ -508,8 +508,8 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should support agentmode alias for agent capability', () => {
|
||||
const resultsAgent = viewModel.fetch('@capability:agent');
|
||||
const resultsAgentMode = viewModel.fetch('@capability:agentmode');
|
||||
const resultsAgent = viewModel.filter('@capability:agent');
|
||||
const resultsAgentMode = viewModel.filter('@capability:agentmode');
|
||||
|
||||
const modelsAgent = resultsAgent.filter(r => !isVendorEntry(r));
|
||||
const modelsAgentMode = resultsAgentMode.filter(r => !isVendorEntry(r));
|
||||
@@ -518,7 +518,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should include matched capabilities in results', () => {
|
||||
const results = viewModel.fetch('@capability:tools @capability:vision');
|
||||
const results = viewModel.filter('@capability:tools @capability:vision');
|
||||
|
||||
const models = results.filter(r => !isVendorEntry(r)) as IModelItemEntry[];
|
||||
assert.ok(models.length > 0);
|
||||
@@ -587,7 +587,7 @@ suite('ChatModelsViewModel', () => {
|
||||
const { viewModel: singleVendorViewModel } = createSingleVendorViewModel(store, chatEntitlementService);
|
||||
await singleVendorViewModel.resolve();
|
||||
|
||||
const results = singleVendorViewModel.fetch('');
|
||||
const results = singleVendorViewModel.filter('');
|
||||
|
||||
// Should have only model entries, no vendor entry
|
||||
const vendors = results.filter(isVendorEntry);
|
||||
@@ -600,7 +600,7 @@ suite('ChatModelsViewModel', () => {
|
||||
|
||||
test('should show vendor headers when multiple vendors exist', () => {
|
||||
// This is the existing behavior test
|
||||
const results = viewModel.fetch('');
|
||||
const results = viewModel.filter('');
|
||||
|
||||
// Should have 2 vendor entries and 4 model entries (grouped by vendor)
|
||||
const vendors = results.filter(isVendorEntry);
|
||||
@@ -617,7 +617,7 @@ suite('ChatModelsViewModel', () => {
|
||||
// Try to collapse the single vendor
|
||||
singleVendorViewModel.toggleVendorCollapsed('copilot');
|
||||
|
||||
const results = singleVendorViewModel.fetch('');
|
||||
const results = singleVendorViewModel.filter('');
|
||||
|
||||
// Should still show models even though vendor is "collapsed"
|
||||
// because there's no vendor header to collapse
|
||||
@@ -632,7 +632,7 @@ suite('ChatModelsViewModel', () => {
|
||||
const { viewModel: singleVendorViewModel } = createSingleVendorViewModel(store, chatEntitlementService);
|
||||
await singleVendorViewModel.resolve();
|
||||
|
||||
const results = singleVendorViewModel.fetch('@capability:agent');
|
||||
const results = singleVendorViewModel.filter('@capability:agent');
|
||||
|
||||
// Should not show vendor header
|
||||
const vendors = results.filter(isVendorEntry);
|
||||
@@ -645,7 +645,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should always place copilot vendor at the top', () => {
|
||||
const results = viewModel.fetch('');
|
||||
const results = viewModel.filter('');
|
||||
|
||||
const vendors = results.filter(isVendorEntry) as IVendorItemEntry[];
|
||||
assert.ok(vendors.length >= 2);
|
||||
@@ -708,7 +708,7 @@ suite('ChatModelsViewModel', () => {
|
||||
|
||||
await viewModel.resolve();
|
||||
|
||||
const results = viewModel.fetch('');
|
||||
const results = viewModel.filter('');
|
||||
const vendors = results.filter(isVendorEntry) as IVendorItemEntry[];
|
||||
|
||||
// Should have 4 vendors: copilot, openai, anthropic, azure
|
||||
@@ -725,7 +725,7 @@ suite('ChatModelsViewModel', () => {
|
||||
|
||||
test('should keep copilot at top even with text search', () => {
|
||||
// Even when searching, if results include multiple vendors, copilot should be first
|
||||
const results = viewModel.fetch('GPT');
|
||||
const results = viewModel.filter('GPT');
|
||||
|
||||
const vendors = results.filter(isVendorEntry) as IVendorItemEntry[];
|
||||
|
||||
@@ -739,7 +739,7 @@ suite('ChatModelsViewModel', () => {
|
||||
});
|
||||
|
||||
test('should keep copilot at top when filtering by capability', () => {
|
||||
const results = viewModel.fetch('@capability:tools');
|
||||
const results = viewModel.filter('@capability:tools');
|
||||
|
||||
const vendors = results.filter(isVendorEntry) as IVendorItemEntry[];
|
||||
|
||||
|
||||
@@ -472,27 +472,11 @@ suite('PromptValidator', () => {
|
||||
assert.strictEqual(markers[0].message, `The 'name' attribute must be a string.`);
|
||||
}
|
||||
|
||||
// Invalid characters in name
|
||||
{
|
||||
const content = [
|
||||
'---',
|
||||
'name: "My@Agent!"',
|
||||
'description: "Test agent"',
|
||||
'target: vscode',
|
||||
'---',
|
||||
'Body',
|
||||
].join('\n');
|
||||
const markers = await validate(content, PromptsType.agent);
|
||||
assert.strictEqual(markers.length, 1);
|
||||
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
|
||||
assert.strictEqual(markers[0].message, `The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods.`);
|
||||
}
|
||||
|
||||
// Valid name with allowed characters
|
||||
{
|
||||
const content = [
|
||||
'---',
|
||||
'name: "My_Agent-2.0"',
|
||||
'name: "My_Agent-2.0 with spaces"',
|
||||
'description: "Test agent"',
|
||||
'target: vscode',
|
||||
'---',
|
||||
@@ -632,22 +616,6 @@ suite('PromptValidator', () => {
|
||||
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
|
||||
assert.strictEqual(markers[0].message, `The 'name' attribute must not be empty.`);
|
||||
}
|
||||
|
||||
// Invalid characters in name
|
||||
{
|
||||
const content = [
|
||||
'---',
|
||||
'name: "My Instructions#"',
|
||||
'description: "Test instructions"',
|
||||
'applyTo: "**/*.ts"',
|
||||
'---',
|
||||
'Body',
|
||||
].join('\n');
|
||||
const markers = await validate(content, PromptsType.instructions);
|
||||
assert.strictEqual(markers.length, 1);
|
||||
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
|
||||
assert.strictEqual(markers[0].message, `The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -786,21 +754,6 @@ suite('PromptValidator', () => {
|
||||
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
|
||||
assert.strictEqual(markers[0].message, `The 'name' attribute must not be empty.`);
|
||||
}
|
||||
|
||||
// Invalid characters in name
|
||||
{
|
||||
const content = [
|
||||
'---',
|
||||
'name: "My Prompt!"',
|
||||
'description: "Test prompt"',
|
||||
'---',
|
||||
'Body',
|
||||
].join('\n');
|
||||
const markers = await validate(content, PromptsType.prompt);
|
||||
assert.strictEqual(markers.length, 1);
|
||||
assert.strictEqual(markers[0].severity, MarkerSeverity.Error);
|
||||
assert.strictEqual(markers[0].message, `The 'name' attribute can only consist of letters, digits, underscores, hyphens, and periods.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -73,8 +73,8 @@ export class MockChatSessionsService implements IChatSessionsService {
|
||||
this.contributions = contributions;
|
||||
}
|
||||
|
||||
async hasChatSessionItemProvider(chatSessionType: string): Promise<boolean> {
|
||||
return this.sessionItemProviders.has(chatSessionType);
|
||||
async activateChatSessionItemProvider(chatSessionType: string): Promise<IChatSessionItemProvider | undefined> {
|
||||
return this.sessionItemProviders.get(chatSessionType);
|
||||
}
|
||||
|
||||
getAllChatSessionItemProviders(): IChatSessionItemProvider[] {
|
||||
|
||||
@@ -543,15 +543,15 @@ export class InlineChatEscapeToolContribution extends Disposable {
|
||||
|
||||
let result: { confirmed: boolean; checkboxChecked?: boolean };
|
||||
if (dontAskAgain !== undefined) {
|
||||
// Use previously stored user preference: true = 'Continue in Chat', false = 'Rephrase' (Cancel)
|
||||
// Use previously stored user preference: true = 'Continue in Chat view', false = 'Rephrase' (Cancel)
|
||||
result = { confirmed: dontAskAgain, checkboxChecked: false };
|
||||
} else {
|
||||
result = await dialogService.confirm({
|
||||
type: 'question',
|
||||
title: localize('confirm.title', "Continue in Panel Chat?"),
|
||||
message: localize('confirm', "Do you want to continue in panel chat or rephrase your prompt?"),
|
||||
detail: localize('confirm.detail', "Inline Chat is designed for single file code changes. This task is either too complex or requires a text response. You can rephrase your prompt or continue in panel chat."),
|
||||
primaryButton: localize('confirm.yes', "Continue in Chat"),
|
||||
title: localize('confirm.title', "Do you want to continue in Chat view?"),
|
||||
message: localize('confirm', "Do you want to continue in Chat view?"),
|
||||
detail: localize('confirm.detail', "Inline chat is designed for making single-file code changes. Continue your request in the Chat view or rephrase it for inline chat."),
|
||||
primaryButton: localize('confirm.yes', "Continue in Chat view"),
|
||||
cancelButton: localize('confirm.cancel', "Cancel"),
|
||||
checkbox: { label: localize('chat.remove.confirmation.checkbox', "Don't ask again"), checked: false },
|
||||
});
|
||||
|
||||
@@ -85,6 +85,7 @@ import { CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatConfigKeys, InlineChatResponse
|
||||
import { TestWorkerService } from './testWorkerService.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { ChatWidgetService } from '../../../chat/browser/chatWidgetService.js';
|
||||
import { ChatContextService, IChatContextService } from '../../../chat/browser/chatContextService.js';
|
||||
|
||||
suite('InlineChatController', function () {
|
||||
|
||||
@@ -256,6 +257,8 @@ suite('InlineChatController', function () {
|
||||
model.setEOL(EndOfLineSequence.LF);
|
||||
editor = store.add(instantiateTestCodeEditor(instaService, model));
|
||||
|
||||
instaService.set(IChatContextService, store.add(instaService.createInstance(ChatContextService)));
|
||||
|
||||
store.add(chatAgentService.registerDynamicAgent({ id: 'testEditorAgent', ...agentData, }, {
|
||||
async invoke(request, progress, history, token) {
|
||||
progress([{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { inputLatency } from '../../../../base/browser/performance.js';
|
||||
import { RunOnceScheduler } from '../../../../base/common/async.js';
|
||||
import { Event } from '../../../../base/common/event.js';
|
||||
import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
|
||||
import { IWorkbenchContribution } from '../../../common/contributions.js';
|
||||
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
@@ -16,6 +17,7 @@ export class InputLatencyContrib extends Disposable implements IWorkbenchContrib
|
||||
private readonly _scheduler: RunOnceScheduler;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService
|
||||
) {
|
||||
@@ -64,16 +66,20 @@ export class InputLatencyContrib extends Disposable implements IWorkbenchContrib
|
||||
render: InputLatencyStatisticFragment;
|
||||
total: InputLatencyStatisticFragment;
|
||||
sampleCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The number of samples measured.' };
|
||||
gpuAcceleration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether GPU acceleration was enabled at the time the event was reported.' };
|
||||
};
|
||||
|
||||
type PerformanceInputLatencyEvent = inputLatency.IInputLatencyMeasurements;
|
||||
type PerformanceInputLatencyEvent = inputLatency.IInputLatencyMeasurements & {
|
||||
gpuAcceleration: boolean;
|
||||
};
|
||||
|
||||
this._telemetryService.publicLog2<PerformanceInputLatencyEvent, PerformanceInputLatencyClassification>('performance.inputLatency', {
|
||||
keydown: measurements.keydown,
|
||||
input: measurements.input,
|
||||
render: measurements.render,
|
||||
total: measurements.total,
|
||||
sampleCount: measurements.sampleCount
|
||||
sampleCount: measurements.sampleCount,
|
||||
gpuAcceleration: this._configurationService.getValue('editor.experimentalGpuAcceleration') === 'on'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +344,18 @@ registerAction2(class extends Action2 {
|
||||
} else {
|
||||
title = getHistoryItemEditorTitle(historyItem);
|
||||
historyItemId = historyItem.id;
|
||||
historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined;
|
||||
|
||||
if (historyItem.parentIds.length > 0) {
|
||||
// History item right above the incoming changes history item
|
||||
if (historyItem.parentIds[0] === SCMIncomingHistoryItemId && historyItemRemoteRef) {
|
||||
historyItemParentId = await historyProvider.resolveHistoryItemRefsCommonAncestor([
|
||||
historyItemRef.name,
|
||||
historyItemRemoteRef.name
|
||||
]);
|
||||
} else {
|
||||
historyItemParentId = historyItem.parentIds[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!title || !historyItemId || !historyItemParentId) {
|
||||
@@ -938,7 +949,24 @@ class SCMHistoryTreeDataSource extends Disposable implements IAsyncDataSource<SC
|
||||
} else {
|
||||
// History item
|
||||
historyItemId = historyItem.id;
|
||||
historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined;
|
||||
|
||||
if (historyItem.parentIds.length > 0) {
|
||||
// History item right above the incoming changes history item
|
||||
if (historyItem.parentIds[0] === SCMIncomingHistoryItemId) {
|
||||
const historyItemRef = historyProvider?.historyItemRef.get();
|
||||
const historyItemRemoteRef = historyProvider?.historyItemRemoteRef.get();
|
||||
|
||||
if (!historyProvider || !historyItemRef || !historyItemRemoteRef) {
|
||||
return [];
|
||||
}
|
||||
|
||||
historyItemParentId = await historyProvider.resolveHistoryItemRefsCommonAncestor([
|
||||
historyItemRef.name,
|
||||
historyItemRemoteRef.name]);
|
||||
} else {
|
||||
historyItemParentId = historyItem.parentIds[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId) ?? [];
|
||||
|
||||
@@ -531,9 +531,6 @@ export class SCMRepositoriesViewPane extends ViewPane {
|
||||
getWidgetAriaLabel() {
|
||||
return localize('scm', "Source Control Repositories");
|
||||
}
|
||||
},
|
||||
twistieAdditionalCssClass: (e: unknown) => {
|
||||
return isSCMRepository(e) ? 'force-twistie' : undefined;
|
||||
}
|
||||
}
|
||||
) as WorkbenchCompressibleAsyncDataTree<ISCMViewService, TreeElement>;
|
||||
|
||||
@@ -2405,9 +2405,7 @@ export class SCMViewPane extends ViewPane {
|
||||
},
|
||||
accessibilityProvider: this.instantiationService.createInstance(SCMAccessibilityProvider),
|
||||
twistieAdditionalCssClass: (e: unknown) => {
|
||||
if (isSCMRepository(e)) {
|
||||
return 'force-twistie';
|
||||
} else if (isSCMActionButton(e) || isSCMInput(e)) {
|
||||
if (isSCMActionButton(e) || isSCMInput(e)) {
|
||||
return 'force-no-twistie';
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ export class DefaultAccountManagementContribution extends Disposable implements
|
||||
return;
|
||||
}
|
||||
|
||||
this.registerSignInAction(defaultAccountProviderId, this.productService.defaultAccount.authenticationProvider.scopes);
|
||||
this.registerSignInAction(defaultAccountProviderId, this.productService.defaultAccount.authenticationProvider.scopes[0]);
|
||||
this.setDefaultAccount(await this.getDefaultAccountFromAuthenticatedSessions(defaultAccountProviderId, this.productService.defaultAccount.authenticationProvider.scopes));
|
||||
|
||||
this._register(this.authenticationService.onDidChangeSessions(async e => {
|
||||
@@ -205,11 +205,10 @@ export class DefaultAccountManagementContribution extends Disposable implements
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getDefaultAccountFromAuthenticatedSessions(authProviderId: string, scopes: string[]): Promise<IDefaultAccount | null> {
|
||||
private async getDefaultAccountFromAuthenticatedSessions(authProviderId: string, scopes: string[][]): Promise<IDefaultAccount | null> {
|
||||
try {
|
||||
this.logService.debug('[DefaultAccount] Getting Default Account from authenticated sessions for provider:', authProviderId);
|
||||
const sessions = await this.authenticationService.getSessions(authProviderId, undefined, undefined, true);
|
||||
const session = sessions.find(s => this.scopesMatch(s.scopes, scopes));
|
||||
const session = await this.findMatchingProviderSession(authProviderId, scopes);
|
||||
|
||||
if (!session) {
|
||||
this.logService.debug('[DefaultAccount] No matching session found for provider:', authProviderId);
|
||||
@@ -239,6 +238,18 @@ export class DefaultAccountManagementContribution extends Disposable implements
|
||||
}
|
||||
}
|
||||
|
||||
private async findMatchingProviderSession(authProviderId: string, allScopes: string[][]): Promise<AuthenticationSession | undefined> {
|
||||
const sessions = await this.authenticationService.getSessions(authProviderId, undefined, undefined, true);
|
||||
for (const session of sessions) {
|
||||
for (const scopes of allScopes) {
|
||||
if (this.scopesMatch(session.scopes, scopes)) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private scopesMatch(scopes: ReadonlyArray<string>, expectedScopes: string[]): boolean {
|
||||
return scopes.length === expectedScopes.length && expectedScopes.every(scope => scopes.includes(scope));
|
||||
}
|
||||
|
||||
+42
-10
@@ -10,28 +10,61 @@ declare module 'vscode' {
|
||||
|
||||
export namespace chat {
|
||||
|
||||
// TODO@alexr00 API:
|
||||
// selector is confusing
|
||||
export function registerChatContextProvider(selector: DocumentSelector, id: string, provider: ChatContextProvider): Disposable;
|
||||
/**
|
||||
* Register a chat context provider. Chat context can be provided:
|
||||
* - For a resource. Make sure to pass a selector that matches the resource you want to provide context for.
|
||||
* Providers registered without a selector will not be called for resource-based context.
|
||||
* - Explicitly. These context items are shown as options when the user explicitly attaches context.
|
||||
*
|
||||
* To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:<id>` activation event in your `package.json`.
|
||||
*
|
||||
* @param selector Optional document selector to filter which resources the provider is called for. If omitted, the provider will only be called for explicit context requests.
|
||||
* @param id Unique identifier for the provider.
|
||||
* @param provider The chat context provider.
|
||||
*/
|
||||
export function registerChatContextProvider(selector: DocumentSelector | undefined, id: string, provider: ChatContextProvider): Disposable;
|
||||
|
||||
}
|
||||
|
||||
export interface ChatContextItem {
|
||||
/**
|
||||
* Icon for the context item.
|
||||
*/
|
||||
icon: ThemeIcon;
|
||||
/**
|
||||
* Human readable label for the context item.
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* An optional description of the context item, e.g. to describe the item to the language model.
|
||||
*/
|
||||
modelDescription?: string;
|
||||
/**
|
||||
* The value of the context item. Can be omitted when returned from one of the `provide` methods if the provider supports `resolveChatContext`.
|
||||
*/
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface ChatContextProvider<T extends ChatContextItem = ChatContextItem> {
|
||||
|
||||
/**
|
||||
* An optional event that should be fired when the workspace chat context has changed.
|
||||
*/
|
||||
onDidChangeWorkspaceChatContext?: Event<void>;
|
||||
|
||||
/**
|
||||
* Provide a list of chat context items to be included as workspace context for all chat sessions.
|
||||
*
|
||||
* @param token A cancellation token.
|
||||
*/
|
||||
provideWorkspaceChatContext?(token: CancellationToken): ProviderResult<T[]>;
|
||||
|
||||
/**
|
||||
* Provide a list of chat context items that a user can choose from. These context items are shown as options when the user explicitly attaches context.
|
||||
* Chat context items can be provided without a `value`, as the `value` can be resolved later using `resolveChatContext`.
|
||||
* `resolveChatContext` is only called for items that do not have a `value`.
|
||||
*
|
||||
* @param options
|
||||
* @param token
|
||||
* @param token A cancellation token.
|
||||
*/
|
||||
provideChatContextExplicit?(token: CancellationToken): ProviderResult<T[]>;
|
||||
|
||||
@@ -40,17 +73,16 @@ declare module 'vscode' {
|
||||
* Chat context items can be provided without a `value`, as the `value` can be resolved later using `resolveChatContext`.
|
||||
* `resolveChatContext` is only called for items that do not have a `value`.
|
||||
*
|
||||
* @param resource
|
||||
* @param options
|
||||
* @param token
|
||||
* @param options Options include the resource for which to provide context.
|
||||
* @param token A cancellation token.
|
||||
*/
|
||||
provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult<T | undefined>;
|
||||
|
||||
/**
|
||||
* If a chat context item is provided without a `value`, from either of the `provide` methods, this method is called to resolve the `value` for the item.
|
||||
*
|
||||
* @param context
|
||||
* @param token
|
||||
* @param context The context item to resolve.
|
||||
* @param token A cancellation token.
|
||||
*/
|
||||
resolveChatContext(context: T, token: CancellationToken): ProviderResult<ChatContextItem>;
|
||||
}
|
||||
|
||||
@@ -245,10 +245,6 @@ declare module 'vscode' {
|
||||
|
||||
export interface ChatContext {
|
||||
readonly chatSessionContext?: ChatSessionContext;
|
||||
readonly chatSummary?: {
|
||||
readonly prompt?: string;
|
||||
readonly history?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChatSessionContext {
|
||||
|
||||
Reference in New Issue
Block a user