Merge remote-tracking branch 'origin/main' into model-selection

This commit is contained in:
Andrew Pareles 2025-04-07 04:00:57 -07:00
commit be07ecec2e
68 changed files with 4332 additions and 3838 deletions

View file

@ -1,412 +0,0 @@
name: VS Code Build
on:
push:
branches: [ main, release/*, github-workflow ]
pull_request:
branches: [ main ]
workflow_dispatch:
inputs:
build_macos:
description: 'Build macOS'
type: boolean
default: true
build_macos_arm64:
description: 'Build macOS ARM64'
type: boolean
default: true
build_macos_universal:
description: 'Build macOS Universal'
type: boolean
default: true
build_linux:
description: 'Build Linux x64'
type: boolean
default: true
build_linux_arm64:
description: 'Build Linux ARM64'
type: boolean
default: false
build_windows:
description: 'Build Windows'
type: boolean
default: true
quality:
description: 'Quality (insider or stable)'
type: choice
options:
- insider
- stable
default: 'insider'
env:
VSCODE_QUALITY: ${{ github.event.inputs.quality }}
NPM_REGISTRY: 'https://registry.npmjs.org/'
VSCODE_ARCH: 'x64'
VSCODE_CIBUILD: false
jobs:
compile:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Install Dependencies
run: |
npm ci
- name: Compile
run: |
npm run compile
- name: Package Compilation Output
run: |
mkdir -p .build
tar -czf compilation.tar.gz .build out-* test/integration/browser/out test/smoke/out test/automation/out
- name: Upload Compilation Artifact
uses: actions/upload-artifact@v4
with:
name: compilation
path: compilation.tar.gz
compile-cli:
runs-on: ubuntu-latest
needs: compile
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install Dependencies
run: |
npm ci
- name: Build CLI
run: |
cd cli
cargo build --release --bin=code
- name: Upload CLI Artifacts
uses: actions/upload-artifact@v4
with:
name: vscode_cli
path: cli/target/release/code
build-macos:
if: ${{ github.event.inputs.build_macos == 'true' }}
runs-on: macos-latest
needs: [compile, compile-cli]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Download Compilation
uses: actions/download-artifact@v4
with:
name: compilation
- name: Extract Compilation
run: tar -xzf compilation.tar.gz
- name: Install Dependencies
run: npm ci
- name: Build macOS x64
run: |
npm run gulp vscode-darwin-x64-min-ci
- name: Download CLI
uses: actions/download-artifact@v4
with:
name: vscode_cli
path: cli-bin
- name: Integrate CLI
run: |
APP_ROOT="$(pwd)/../VSCode-darwin-x64"
APP_NAME="`ls $APP_ROOT | head -n 1`"
APP_PATH="$APP_ROOT/$APP_NAME"
CLI_APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").tunnelApplicationName")
mkdir -p "$APP_PATH/Contents/Resources/app/bin"
cp cli-bin/code "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME"
chmod +x "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME"
- name: Package macOS App
run: |
ARCHIVE_PATH="VSCode-darwin-x64.zip"
(cd ../VSCode-darwin-x64 && zip -r -X -y $(pwd)/$ARCHIVE_PATH *)
- name: Upload macOS App
uses: actions/upload-artifact@v4
with:
name: vscode-darwin-x64
path: VSCode-darwin-x64.zip
build-macos-arm64:
if: ${{ github.event.inputs.build_macos_arm64 == 'true' }}
runs-on: macos-latest
needs: [compile, compile-cli]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Download Compilation
uses: actions/download-artifact@v4
with:
name: compilation
- name: Extract Compilation
run: tar -xzf compilation.tar.gz
- name: Install Dependencies
run: npm ci
- name: Build macOS ARM64
run: |
npm run gulp vscode-darwin-arm64-min-ci
- name: Download CLI
uses: actions/download-artifact@v4
with:
name: vscode_cli
path: cli-bin
- name: Integrate CLI
run: |
APP_ROOT="$(pwd)/../VSCode-darwin-arm64"
APP_NAME="`ls $APP_ROOT | head -n 1`"
APP_PATH="$APP_ROOT/$APP_NAME"
CLI_APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").tunnelApplicationName")
mkdir -p "$APP_PATH/Contents/Resources/app/bin"
cp cli-bin/code "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME"
chmod +x "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME"
- name: Package macOS App
run: |
ARCHIVE_PATH="VSCode-darwin-arm64.zip"
(cd ../VSCode-darwin-arm64 && zip -r -X -y $(pwd)/$ARCHIVE_PATH *)
- name: Upload macOS App
uses: actions/upload-artifact@v4
with:
name: vscode-darwin-arm64
path: VSCode-darwin-arm64.zip
build-macos-universal:
if: ${{ github.event.inputs.build_macos_universal == 'true' && github.event.inputs.build_macos == 'true' && github.event.inputs.build_macos_arm64 == 'true' }}
runs-on: macos-latest
needs: [build-macos, build-macos-arm64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Download x64 Build
uses: actions/download-artifact@v4
with:
name: vscode-darwin-x64
- name: Download ARM64 Build
uses: actions/download-artifact@v4
with:
name: vscode-darwin-arm64
- name: Extract Builds
run: |
mkdir -p VSCode-darwin-x64
mkdir -p VSCode-darwin-arm64
unzip VSCode-darwin-x64.zip -d VSCode-darwin-x64
unzip VSCode-darwin-arm64.zip -d VSCode-darwin-arm64
- name: Install Dependencies
run: npm ci
- name: Create Universal Build
run: |
node build/darwin/create-universal-app.js $(pwd)
- name: Package Universal App
run: |
ARCHIVE_PATH="VSCode-darwin-universal.zip"
(cd VSCode-darwin-universal && zip -r -X -y $(pwd)/$ARCHIVE_PATH *)
- name: Upload Universal App
uses: actions/upload-artifact@v4
with:
name: vscode-darwin-universal
path: VSCode-darwin-universal.zip
build-linux:
if: ${{ github.event.inputs.build_linux == 'true' }}
runs-on: ubuntu-latest
needs: [compile, compile-cli]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1
npm ci
- name: Download Compilation
uses: actions/download-artifact@v4
with:
name: compilation
- name: Extract Compilation
run: tar -xzf compilation.tar.gz
- name: Build Linux x64
run: |
npm run gulp vscode-linux-x64-min-ci
- name: Download CLI
uses: actions/download-artifact@v4
with:
name: vscode_cli
path: cli-bin
- name: Integrate CLI
run: |
CLI_APP_NAME=$(node -p "require(\"../VSCode-linux-x64/resources/app/product.json\").tunnelApplicationName")
mkdir -p "../VSCode-linux-x64/bin"
cp cli-bin/code "../VSCode-linux-x64/bin/$CLI_APP_NAME"
chmod +x "../VSCode-linux-x64/bin/$CLI_APP_NAME"
- name: Create .tar.gz Archive
run: |
ARCHIVE_PATH="VSCode-linux-x64.tar.gz"
(cd .. && tar -czf $(pwd)/$ARCHIVE_PATH VSCode-linux-x64)
- name: Upload Linux Build
uses: actions/upload-artifact@v4
with:
name: vscode-linux-x64-archive
path: VSCode-linux-x64.tar.gz
- name: Build .deb Package
run: |
npm run gulp vscode-linux-x64-prepare-deb
npm run gulp vscode-linux-x64-build-deb
- name: Upload .deb Package
uses: actions/upload-artifact@v4
with:
name: vscode-linux-x64-deb
path: .build/linux/deb/*/deb/*.deb
- name: Build .rpm Package
run: |
npm run gulp vscode-linux-x64-prepare-rpm
npm run gulp vscode-linux-x64-build-rpm
- name: Upload .rpm Package
uses: actions/upload-artifact@v4
with:
name: vscode-linux-x64-rpm
path: .build/linux/rpm/*/*.rpm
build-windows:
if: ${{ github.event.inputs.build_windows == 'true' }}
runs-on: windows-latest
needs: [compile, compile-cli]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Download Compilation
uses: actions/download-artifact@v4
with:
name: compilation
- name: Extract Compilation
shell: powershell
run: tar -xzf compilation.tar.gz
- name: Install Dependencies
run: npm ci
- name: Build Windows x64
run: |
npm run gulp vscode-win32-x64-min-ci
npm run gulp vscode-win32-x64-inno-updater
- name: Download CLI
uses: actions/download-artifact@v4
with:
name: vscode_cli
path: cli-bin
- name: Integrate CLI
shell: powershell
run: |
$AppProductJson = Get-Content -Raw -Path "../VSCode-win32-x64/resources/app/product.json" | ConvertFrom-Json
$CliAppName = $AppProductJson.tunnelApplicationName
$AppName = $AppProductJson.applicationName
mkdir -Force "../VSCode-win32-x64/bin"
Copy-Item -Path "cli-bin/code" -Destination "../VSCode-win32-x64/bin/$CliAppName.exe"
- name: Package Windows Build
shell: powershell
run: |
$ArchivePath = "VSCode-win32-x64.zip"
Compress-Archive -Path "../VSCode-win32-x64/*" -DestinationPath $ArchivePath
- name: Upload Windows Build
uses: actions/upload-artifact@v4
with:
name: vscode-win32-x64-archive
path: VSCode-win32-x64.zip
- name: Build User Setup
run: |
npm run gulp vscode-win32-x64-user-setup
- name: Upload User Setup
uses: actions/upload-artifact@v4
with:
name: vscode-win32-x64-user-setup
path: .build/win32-x64/user-setup/VSCodeSetup.exe

View file

@ -1,527 +0,0 @@
name: Build Void
on:
push:
branches: [ main, release/*, github-workflow ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev
# Set npm config to use GitHub token for authentication to avoid rate limits
npm config set //github.com/:_authToken=${{ github.token }}
npm config set //api.github.com/:_authToken=${{ github.token }}
npm config set //npm.pkg.github.com/:_authToken=${{ github.token }}
# Configure npm to use the GitHub token for all requests to github.com domains
npm config set @microsoft:registry https://npm.pkg.github.com
npm config set @vscode:registry https://npm.pkg.github.com
# Increase network timeout to handle slow connections
npm config set fetch-timeout 300000
npm config set fetch-retry-mintimeout 20000
npm config set fetch-retry-maxtimeout 120000
npm install
npm install -g node-gyp
npm install -g gulp-cli
- name: Build
run: |
npm run buildreact
npm run gulp vscode-linux-x64-min
- name: Package
run: |
mkdir -p .build/linux/client
ARCHIVE_PATH=".build/linux/client/void-linux-x64.tar.gz"
tar -czf $ARCHIVE_PATH -C .. VSCode-linux-x64
- name: Generate checksum
run: |
cd .build/linux/client
sha256sum void-linux-x64.tar.gz > void-linux-x64.tar.gz.sha256
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: void-linux-x64
path: |
.build/linux/client/void-linux-x64.tar.gz
.build/linux/client/void-linux-x64.tar.gz.sha256
build-linux-arm64:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libx11-dev libxkbfile-dev libsecret-1-dev libkrb5-dev
# Set npm config to use GitHub token for authentication to avoid rate limits
npm config set //github.com/:_authToken=${{ github.token }}
npm config set //api.github.com/:_authToken=${{ github.token }}
npm config set //npm.pkg.github.com/:_authToken=${{ github.token }}
# Configure npm to use the GitHub token for all requests to github.com domains
npm config set @microsoft:registry https://npm.pkg.github.com
npm config set @vscode:registry https://npm.pkg.github.com
# Increase network timeout to handle slow connections
npm config set fetch-timeout 300000
npm config set fetch-retry-mintimeout 20000
npm config set fetch-retry-maxtimeout 120000
npm install
npm install -g node-gyp
npm install -g gulp-cli
- name: Build
run: |
npm run buildreact
npm run gulp vscode-linux-arm64-min
- name: Package
run: |
mkdir -p .build/linux/client
ARCHIVE_PATH=".build/linux/client/void-linux-arm64.tar.gz"
tar -czf $ARCHIVE_PATH -C .. VSCode-linux-arm64
- name: Generate checksum
run: |
cd .build/linux/client
sha256sum void-linux-arm64.tar.gz > void-linux-arm64.tar.gz.sha256
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: void-linux-arm64
path: |
.build/linux/client/void-linux-arm64.tar.gz
.build/linux/client/void-linux-arm64.tar.gz.sha256
build-windows:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
run: |
# Set npm config to use GitHub token for authentication to avoid rate limits
npm config set //github.com/:_authToken=${{ github.token }}
npm config set //api.github.com/:_authToken=${{ github.token }}
npm config set //npm.pkg.github.com/:_authToken=${{ github.token }}
# Configure npm to use the GitHub token for all requests to github.com domains
npm config set @microsoft:registry https://npm.pkg.github.com
npm config set @vscode:registry https://npm.pkg.github.com
# Increase network timeout to handle slow connections
npm config set fetch-timeout 300000
npm config set fetch-retry-mintimeout 20000
npm config set fetch-retry-maxtimeout 120000
npm install
npm install -g node-gyp
npm install -g gulp-cli
- name: Build
run: |
npm run buildreact
npm run gulp vscode-win32-x64-min
- name: Package
run: |
mkdir -p .build/win32-x64
Compress-Archive -Path ..\VSCode-win32-x64\* -DestinationPath .build\win32-x64\void-win32-x64.zip
shell: pwsh
- name: Generate checksum
run: |
cd .build/win32-x64
$hash = Get-FileHash -Algorithm SHA256 void-win32-x64.zip
$hash.Hash | Out-File -Encoding ascii void-win32-x64.zip.sha256
shell: pwsh
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: void-win32-x64
path: |
.build/win32-x64/void-win32-x64.zip
.build/win32-x64/void-win32-x64.zip.sha256
build-windows-arm64:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
run: |
# Set npm config to use GitHub token for authentication to avoid rate limits
npm config set //github.com/:_authToken=${{ github.token }}
npm config set //api.github.com/:_authToken=${{ github.token }}
npm config set //npm.pkg.github.com/:_authToken=${{ github.token }}
# Configure npm to use the GitHub token for all requests to github.com domains
npm config set @microsoft:registry https://npm.pkg.github.com
npm config set @vscode:registry https://npm.pkg.github.com
# Increase network timeout to handle slow connections
npm config set fetch-timeout 300000
npm config set fetch-retry-mintimeout 20000
npm config set fetch-retry-maxtimeout 120000
npm install
npm install -g node-gyp
npm install -g gulp-cli
- name: Build
run: |
npm run buildreact
npm run gulp vscode-win32-arm64-min
- name: Package
run: |
mkdir -p .build/win32-arm64
Compress-Archive -Path ..\VSCode-win32-arm64\* -DestinationPath .build\win32-arm64\void-win32-arm64.zip
shell: pwsh
- name: Generate checksum
run: |
cd .build/win32-arm64
$hash = Get-FileHash -Algorithm SHA256 void-win32-arm64.zip
$hash.Hash | Out-File -Encoding ascii void-win32-arm64.zip.sha256
shell: pwsh
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: void-win32-arm64
path: |
.build/win32-arm64/void-win32-arm64.zip
.build/win32-arm64/void-win32-arm64.zip.sha256
build-macos:
runs-on: macos-latest-large
strategy:
matrix:
arch: [arm64, x64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
run: |
# Set npm config to use GitHub token for authentication to avoid rate limits
npm config set //github.com/:_authToken=${{ github.token }}
npm config set //api.github.com/:_authToken=${{ github.token }}
npm config set //npm.pkg.github.com/:_authToken=${{ github.token }}
# Configure npm to use the GitHub token for all requests to github.com domains
npm config set @microsoft:registry https://npm.pkg.github.com
npm config set @vscode:registry https://npm.pkg.github.com
# Increase network timeout to handle slow connections
npm config set fetch-timeout 300000
npm config set fetch-retry-mintimeout 20000
npm config set fetch-retry-maxtimeout 120000
npm install
npm install -g node-gyp
npm install -g gulp-cli
npm install -g create-dmg
- name: Build
run: |
npm run buildreact
npm run gulp vscode-darwin-${{ matrix.arch }}-min
- name: Create temporary working directory
run: |
WORKING_DIR="${{ runner.temp }}/VoidSign-${{ matrix.arch }}"
KEYCHAIN_DIR="${WORKING_DIR}/1_Keychain"
SIGN_DIR="${WORKING_DIR}/2_Signed"
mkdir -p "${WORKING_DIR}" "${KEYCHAIN_DIR}" "${SIGN_DIR}"
cp -Rp "$(pwd)/../VSCode-darwin-${{ matrix.arch }}" "${SIGN_DIR}"
echo "WORKING_DIR=${WORKING_DIR}" >> $GITHUB_ENV
echo "KEYCHAIN_DIR=${KEYCHAIN_DIR}" >> $GITHUB_ENV
echo "SIGN_DIR=${SIGN_DIR}" >> $GITHUB_ENV
echo "SIGNED_DOTAPP_DIR=${SIGN_DIR}/VSCode-darwin-${{ matrix.arch }}" >> $GITHUB_ENV
echo "SIGNED_DOTAPP=${SIGN_DIR}/VSCode-darwin-${{ matrix.arch }}/Void.app" >> $GITHUB_ENV
- name: Import certificate
if: ${{ github.event_name != 'pull_request' && github.repository == 'voideditor/void' }}
env:
P12_BASE64: ${{ secrets.MACOS_CERTIFICATE }}
P12_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
KEYCHAIN_PASSWORD: "temporary-password"
run: |
KEYCHAIN="${KEYCHAIN_DIR}/buildagent.keychain"
echo "KEYCHAIN=${KEYCHAIN}" >> $GITHUB_ENV
# Create a new keychain
security create-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN}"
security set-keychain-settings -lut 21600 "${KEYCHAIN}"
security unlock-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN}"
# Import certificate
echo "${P12_BASE64}" | base64 --decode > "${KEYCHAIN_DIR}/certificate.p12"
security import "${KEYCHAIN_DIR}/certificate.p12" -k "${KEYCHAIN}" -P "${P12_PASSWORD}" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${KEYCHAIN_PASSWORD}" "${KEYCHAIN}" > /dev/null
# Add to keychain list
security list-keychains -d user -s "${KEYCHAIN}" $(security list-keychains -d user | sed s/\"//g)
- name: Sign Application
if: ${{ github.event_name != 'pull_request' && github.repository == 'voideditor/void' }}
env:
CODESIGN_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
VSCODE_ARCH: ${{ matrix.arch }}
run: |
export AGENT_TEMPDIRECTORY="${KEYCHAIN_DIR}"
cd $(pwd)/build/darwin
node sign.js "${SIGN_DIR}"
codesign --verify --verbose=4 "${SIGNED_DOTAPP}"
- name: Create Unsigned App (for PR builds)
if: ${{ github.event_name == 'pull_request' || github.repository != 'voideditor/void' }}
run: |
cp -Rp "$(pwd)/../VSCode-darwin-${{ matrix.arch }}" "${SIGN_DIR}"
echo "SIGNED_DOTAPP_DIR=$(pwd)/../VSCode-darwin-${{ matrix.arch }}" >> $GITHUB_ENV
echo "SIGNED_DOTAPP=$(pwd)/../VSCode-darwin-${{ matrix.arch }}/Void.app" >> $GITHUB_ENV
- name: Create DMG
run: |
cd "${SIGNED_DOTAPP_DIR}"
npx create-dmg --volname "Void Installer" "${SIGNED_DOTAPP}" . || true
GENERATED_DMG=$(ls *.dmg)
mv "${GENERATED_DMG}" "Void-Installer-darwin-${{ matrix.arch }}.dmg"
if [[ "${{ github.event_name }}" != "pull_request" && "${{ github.repository }}" == "voideditor/void" ]]; then
codesign --verify --verbose=4 "Void-Installer-darwin-${{ matrix.arch }}.dmg"
fi
echo "SIGNED_DMG=${SIGNED_DOTAPP_DIR}/Void-Installer-darwin-${{ matrix.arch }}.dmg" >> $GITHUB_ENV
- name: Notarize
if: ${{ github.event_name != 'pull_request' && github.repository == 'voideditor/void' }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APP_PASSWORD: ${{ secrets.APPLE_APP_PWD }}
KEYCHAIN_PASSWORD: "temporary-password"
run: |
# Store credentials for notarization
xcrun notarytool store-credentials "Void" \
--apple-id "${APPLE_ID}" \
--team-id "${TEAM_ID}" \
--password "${APP_PASSWORD}" \
--keychain "${KEYCHAIN}"
# Submit for notarization with a timeout
xcrun notarytool submit "${SIGNED_DMG}" \
--keychain-profile "Void" \
--keychain "${KEYCHAIN}" \
--wait --timeout 2h
# Staple the notarization ticket
xcrun stapler staple "${SIGNED_DMG}"
- name: Create Raw App Archive
run: |
cd "${SIGNED_DOTAPP_DIR}"
VOIDAPP=$(basename "${SIGNED_DOTAPP}")
ditto -c -k --sequesterRsrc --keepParent "${VOIDAPP}" "Void-RawApp-darwin-${{ matrix.arch }}.zip"
- name: Generate Hash File
run: |
cd "${SIGNED_DOTAPP_DIR}"
SHA1=$(shasum -a 1 "Void-RawApp-darwin-${{ matrix.arch }}.zip" | cut -d' ' -f1)
SHA256=$(shasum -a 256 "Void-RawApp-darwin-${{ matrix.arch }}.zip" | cut -d' ' -f1)
TIMESTAMP=$(date +%s)
cat > "Void-UpdJSON-darwin-${{ matrix.arch }}.json" << EOF
{
"sha256hash": "${SHA256}",
"hash": "${SHA1}",
"timestamp": ${TIMESTAMP}
}
EOF
- name: Generate checksum for DMG
run: |
cd "${SIGNED_DOTAPP_DIR}"
shasum -a 256 "Void-Installer-darwin-${{ matrix.arch }}.dmg" > "Void-Installer-darwin-${{ matrix.arch }}.dmg.sha256"
- name: Upload DMG
uses: actions/upload-artifact@v4
with:
name: void-darwin-${{ matrix.arch }}-dmg
path: |
${{ env.SIGNED_DMG }}
${{ env.SIGNED_DOTAPP_DIR }}/Void-Installer-darwin-${{ matrix.arch }}.dmg.sha256
- name: Upload Raw App
uses: actions/upload-artifact@v4
with:
name: void-darwin-${{ matrix.arch }}-rawapp
path: ${{ env.SIGNED_DOTAPP_DIR }}/Void-RawApp-darwin-${{ matrix.arch }}.zip
- name: Upload Hash File
uses: actions/upload-artifact@v4
with:
name: void-darwin-${{ matrix.arch }}-hash
path: ${{ env.SIGNED_DOTAPP_DIR }}/Void-UpdJSON-darwin-${{ matrix.arch }}.json
create-universal-macos:
needs: build-macos
runs-on: macos-latest-large
if: ${{ github.event_name != 'pull_request' && github.repository == 'voideditor/void' }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Download x64 DMG
uses: actions/download-artifact@v4
with:
name: void-darwin-x64-dmg
path: .build/darwin-x64
- name: Download arm64 DMG
uses: actions/download-artifact@v4
with:
name: void-darwin-arm64-dmg
path: .build/darwin-arm64
- name: Download x64 App
uses: actions/download-artifact@v4
with:
name: void-darwin-x64-rawapp
path: .build/darwin-x64-app
- name: Download arm64 App
uses: actions/download-artifact@v4
with:
name: void-darwin-arm64-rawapp
path: .build/darwin-arm64-app
- name: Create Universal App working dir
run: |
mkdir -p .build/darwin-universal/{x64,arm64,universal}
- name: Extract Apps
run: |
unzip -o .build/darwin-x64-app/Void-RawApp-darwin-x64.zip -d .build/darwin-universal/x64
unzip -o .build/darwin-arm64-app/Void-RawApp-darwin-arm64.zip -d .build/darwin-universal/arm64
- name: Install dependencies for universal app
run: |
cd build/
# Set npm config to use GitHub token for authentication to avoid rate limits
npm config set //github.com/:_authToken=${{ github.token }}
npm config set //api.github.com/:_authToken=${{ github.token }}
npm config set //npm.pkg.github.com/:_authToken=${{ github.token }}
# Configure npm to use the GitHub token for all requests to github.com domains
npm config set @microsoft:registry https://npm.pkg.github.com
npm config set @vscode:registry https://npm.pkg.github.com
# Increase network timeout to handle slow connections
npm config set fetch-timeout 300000
npm config set fetch-retry-mintimeout 20000
npm config set fetch-retry-maxtimeout 120000
npm install
npm install -g create-dmg
npm run compile
- name: Create Universal App
run: |
# Script to create universal binary
cd build/darwin
node create-universal-app.js \
"$(pwd)/../../.build/darwin-universal/arm64/Void.app" \
"$(pwd)/../../.build/darwin-universal/x64/Void.app" \
"$(pwd)/../../.build/darwin-universal/universal/Void.app"
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Create Universal DMG
run: |
cd .build/darwin-universal/universal
create-dmg --volname "Void Installer" Void.app . || true
GENERATED_DMG=$(ls *.dmg)
mv "${GENERATED_DMG}" "../../Void-Installer-darwin-universal.dmg"
cd ../..
shasum -a 256 Void-Installer-darwin-universal.dmg > Void-Installer-darwin-universal.dmg.sha256
- name: Upload Universal DMG
uses: actions/upload-artifact@v4
with:
name: void-darwin-universal
path: |
.build/Void-Installer-darwin-universal.dmg
.build/Void-Installer-darwin-universal.dmg.sha256
create-release:
needs: [build-linux, build-linux-arm64, build-windows, build-windows-arm64, build-macos, create-universal-macos]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: release-artifacts
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: |
release-artifacts/void-linux-x64/void-linux-x64.tar.gz
release-artifacts/void-linux-x64/void-linux-x64.tar.gz.sha256
release-artifacts/void-linux-arm64/void-linux-arm64.tar.gz
release-artifacts/void-linux-arm64/void-linux-arm64.tar.gz.sha256
release-artifacts/void-win32-x64/void-win32-x64.zip
release-artifacts/void-win32-x64/void-win32-x64.zip.sha256
release-artifacts/void-win32-arm64/void-win32-arm64.zip
release-artifacts/void-win32-arm64/void-win32-arm64.zip.sha256
release-artifacts/void-darwin-x64-dmg/Void-Installer-darwin-x64.dmg
release-artifacts/void-darwin-x64-dmg/Void-Installer-darwin-x64.dmg.sha256
release-artifacts/void-darwin-arm64-dmg/Void-Installer-darwin-arm64.dmg
release-artifacts/void-darwin-arm64-dmg/Void-Installer-darwin-arm64.dmg.sha256
release-artifacts/void-darwin-universal/Void-Installer-darwin-universal.dmg
release-artifacts/void-darwin-universal/Void-Installer-darwin-universal.dmg.sha256
draft: true
generate_release_notes: true

2
.npmrc
View file

@ -1,5 +1,5 @@
disturl="https://electronjs.org/headers"
target="34.2.0"
target="34.3.2"
ms_build_id="11044223"
runtime="electron"
build_from_source="true"

View file

@ -90,7 +90,7 @@ Alternatively, if you want to build Void from the terminal, instead of pressing
- If you get `"TypeError: Failed to fetch dynamically imported module"`, make sure all imports end with `.js`.
- If you see missing styles, wait a few seconds and then reload.
- If you have any questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new). You can also refer to VSCode's complete [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page.
- If you get errors like `npm error libtool: error: unrecognised option: '-static'`, make sure you have GNU libtool instead of BSD libtool (BSD is the default in macos)
## Packaging
@ -104,12 +104,11 @@ We don't usually recommend packaging. Instead, you should probably just build. I
### Windows
- `npm run gulp vscode-win32-x64` - most common
- `npm run gulp vscode-win32-ia32`
- `npm run gulp vscode-win32-arm64`
### Linux
- `npm run gulp vscode-linux-x64` - most common
- `npm run gulp vscode-linux-arm`
- `npm run gulp vscode-linux-ia32`
- `npm run gulp vscode-linux-arm64`
### Output

View file

@ -1,75 +1,75 @@
c0a187acca68906c4a6387e8fabd052cb031ace6132d60a71001d9a0e891958e *chromedriver-v34.2.0-darwin-arm64.zip
fa5a46d752267d8497d375e19079e8b6a8df70c234a79b2d6b48f5862e1a0abc *chromedriver-v34.2.0-darwin-x64.zip
61e03d4fa570976d80f740637f56192b6448a05a73d1fba9717900b29f2b1b4d *chromedriver-v34.2.0-linux-arm64.zip
ec774d9b1a1b828a0db1502a1017fcab1dfed99b1b6b2fd2308dd600a1efa98a *chromedriver-v34.2.0-linux-armv7l.zip
cc15a6e6206485a2d96649ceb60509b9da04fa2811c4824b2e0eb43d1f4b1417 *chromedriver-v34.2.0-linux-x64.zip
9777122f6684180ef375b9b21dcabbc731d8a8befa300d1d47ad954a5b64c1c8 *chromedriver-v34.2.0-mas-arm64.zip
69451fa148b105fec9644646b22ca758a206499574c5816591354835c8056679 *chromedriver-v34.2.0-mas-x64.zip
eb7adc7e720f5e0f1d2c12ecbe886bdc01f2c9aaa3954bd6ebd313750bb18819 *chromedriver-v34.2.0-win32-arm64.zip
cb1973b0c2f5565974d5c2cb51816692f064b6cdc7897fa341d528ba7f9b14bf *chromedriver-v34.2.0-win32-ia32.zip
af5575b4727c3dbe7272016cbbaa8872043f843168a47d86748a50397efb4f77 *chromedriver-v34.2.0-win32-x64.zip
fcc718af2a28fb953290dc971e945818b4dbb293f297e6e25acb669d450cc0dd *electron-api.json
fa47e752e559a6472b87d8907f63296ed8cd53ecf862a4ae113018474d40aa8e *electron-v34.2.0-darwin-arm64-dsym-snapshot.zip
336c3374e721e2379901141be6345459f78d243b037c65b55bac4ae8cb14bfbe *electron-v34.2.0-darwin-arm64-dsym.zip
ac3b9d712d9f036f066d8eba42797117a513e2d250fcc117f0354300b7d5c1e5 *electron-v34.2.0-darwin-arm64-symbols.zip
ee447c17b2ac545e48083113d7e39a916821c1316f60f42cbcbee4fffe7c022a *electron-v34.2.0-darwin-arm64.zip
d7510bc038d06b26690ac9a78fbb256626502303ff7f5b1c2242f64a02832b1b *electron-v34.2.0-darwin-x64-dsym-snapshot.zip
0e95c2bbda00afe78e6229b824e3ffe0cc8612956dc11a5a30380224acdbecae *electron-v34.2.0-darwin-x64-dsym.zip
6d734ef8e8fd007071aae9a13534894dd801f11900f3e73e49cf5352b426e575 *electron-v34.2.0-darwin-x64-symbols.zip
8ef741819c8a5370dabc3b9df5e6ac217366477c5d5c656ed23c800bc984d887 *electron-v34.2.0-darwin-x64.zip
c2f448882a0392ebfd9810058a6a9580b087002c74fca3dcd3cf4ba5c3b27a7c *electron-v34.2.0-linux-arm64-debug.zip
51e887c382593021127593ceba89ad662d3a6de0f748b94fe3a0ce78a7393923 *electron-v34.2.0-linux-arm64-symbols.zip
818c91309da8ff948c43df58a996c05c0d27daa690e1d659355d9db01e351919 *electron-v34.2.0-linux-arm64.zip
c2f448882a0392ebfd9810058a6a9580b087002c74fca3dcd3cf4ba5c3b27a7c *electron-v34.2.0-linux-armv7l-debug.zip
71ec2b7473db766194bcf04648229c4affedce06e150e692745cc72dbc3749c5 *electron-v34.2.0-linux-armv7l-symbols.zip
0c75996c6bf2d37d0441d3e1021386a9348f8d21783d75571cdb10ede606424f *electron-v34.2.0-linux-armv7l.zip
d1f17be8df8ec6dc1a23afd8a7752f0b949c755a493b8a2c1e83f76c629258ca *electron-v34.2.0-linux-x64-debug.zip
d8fa79154b0b663dbd0054d69f53aec326997449ef21943d27225a2d6899660f *electron-v34.2.0-linux-x64-symbols.zip
f12a02d86cc657500978d263ec6d1841b6e2085cd3bd4d30a97cfe14685396f3 *electron-v34.2.0-linux-x64.zip
71ef8bfebb8513a405fd2beb44ad18f802bbac2248f81a5328ddaaa12906d70c *electron-v34.2.0-mas-arm64-dsym-snapshot.zip
437aca6cad3158f15fd852e5913462637052940f812b20ccc10fccc133d5a0d6 *electron-v34.2.0-mas-arm64-dsym.zip
7f253375a7b43d34b770c03153e47185be7a64bc0c10a783993a93144eb35492 *electron-v34.2.0-mas-arm64-symbols.zip
1f601c20430b036b485c7dc02a39a297f0d08905462a4568306c12f299375e2f *electron-v34.2.0-mas-arm64.zip
d32664181804a17f825bf1fbfd8f0cbe01e0b41b284937359e6d9754b3481ed8 *electron-v34.2.0-mas-x64-dsym-snapshot.zip
f6b394b89fb77dfeefdf525739fe8cd9695f8c2ac2251ed7c571a376cde059e9 *electron-v34.2.0-mas-x64-dsym.zip
539328d93e9bc122e6e34faeab6a42f4845c523f796c7a11bbdcfe5e856f6b98 *electron-v34.2.0-mas-x64-symbols.zip
749b6ced7a9d35a719ad98d4c3bf1a08c315c19ce97d2497235d4ac302cda665 *electron-v34.2.0-mas-x64.zip
e843ea4cb7a93686728d056c957c124760886167932ff619b518e45917edf38a *electron-v34.2.0-win32-arm64-pdb.zip
4908423be5f8ad1b5dd737edfd69ee0870460e16bb639bb963b0981bda2628e4 *electron-v34.2.0-win32-arm64-symbols.zip
9d13b2bd61416eec28f43faa399cc5c0dc9e36dec226314bbf397828f55d74de *electron-v34.2.0-win32-arm64-toolchain-profile.zip
1629cec7b5620e6ca3b5305c393ae147d1a3871a8f164d686b7bee3810fc1109 *electron-v34.2.0-win32-arm64.zip
e7182f1ef5ed13187fe12f35133cefb50c59e1d52ada758cbb72813dda575205 *electron-v34.2.0-win32-ia32-pdb.zip
a10f778f62bf060a7e38f5ea75ea472679b9ef4f12767ee0703e6d77effae78a *electron-v34.2.0-win32-ia32-symbols.zip
9d13b2bd61416eec28f43faa399cc5c0dc9e36dec226314bbf397828f55d74de *electron-v34.2.0-win32-ia32-toolchain-profile.zip
96396712a0240f04471f71246b3885a513b08e3f2d40154272d34e59419db7e6 *electron-v34.2.0-win32-ia32.zip
0c55ef5b1a6ea4604e3f0e9f8b5e946944abd1cfc3b974432d40e924a9996812 *electron-v34.2.0-win32-x64-pdb.zip
d6b67cf12edabcc62ae21e7c90ac6b1161dbefe4e6b765c69fc7040096b7d026 *electron-v34.2.0-win32-x64-symbols.zip
9d13b2bd61416eec28f43faa399cc5c0dc9e36dec226314bbf397828f55d74de *electron-v34.2.0-win32-x64-toolchain-profile.zip
a4fdf617dca787b7f1c6f8d0d1cb69c8adb37ee23f9553fe69803f9cad713360 *electron-v34.2.0-win32-x64.zip
5ca44a4ee9cc74a56c9556ce3f3774986dbb8b4f4953088c7f4577bfba4356a8 *electron.d.ts
86247a6815cc98f321374edcbfc0ab7ee79bcb13a5c25213f42151e4059ec690 *ffmpeg-v34.2.0-darwin-arm64.zip
e94f4707a91194f97d0451f9d5a27982c873a5c13d83d20926ce6fdb613f536c *ffmpeg-v34.2.0-darwin-x64.zip
947d7b7cb035eab94cc15b602dfa8a925cf238c1d9225c01732fe0c41f59c571 *ffmpeg-v34.2.0-linux-arm64.zip
64d74b6b94ac4fd755b97e0ec8d50af9cd73810fdb52f6c081b13a3337ace0ea *ffmpeg-v34.2.0-linux-armv7l.zip
a62cb20c5e5f2ba6f1df6f1bc406cc30f7ed44fe6380b506afa333d1b4ad7a87 *ffmpeg-v34.2.0-linux-x64.zip
715bbb3d193b1ca57d4b329edd05cd6b125dc188cdb73d1c66f220f62c8562e3 *ffmpeg-v34.2.0-mas-arm64.zip
866bf47106e8c3e50f046783eb8f5c756069c6de962a46f4edc7a5ee7e4593ee *ffmpeg-v34.2.0-mas-x64.zip
71a2a6c4cca15ccbcbf8912f5d73f855b0ca79804f941f68527ae808f8163957 *ffmpeg-v34.2.0-win32-arm64.zip
399f841cca166781078ca42c8ea060e3d5850ec6cb79716186d0806f3ce20842 *ffmpeg-v34.2.0-win32-ia32.zip
afad2a44f20a0c0c01fb1fea637f3f821842399593c9961c74d650ca12d32cbb *ffmpeg-v34.2.0-win32-x64.zip
c95fdc9dba05aa68aeccb69d4c34f0cb1fb98d7f5291d974d0b638488693655f *hunspell_dictionaries.zip
0abe74138afdb6e45a085d77407659f13c75ab96f694313d4e98bd662f9c6df2 *libcxx-objects-v34.2.0-linux-arm64.zip
3d0cbf6fb150b006428eab78235856d2204d5e93ca85f162e429b4c8bd9b0d3b *libcxx-objects-v34.2.0-linux-armv7l.zip
3a5491e32cec825499919be1b8bf0550d28fe5a31ff00a95572d49a58bb4820a *libcxx-objects-v34.2.0-linux-x64.zip
ff8753d52f759041b8e5462125ee2b96798fe8b5047f8fb8ae60cd07af2fb13d *libcxx_headers.zip
c98cce0091681bc367a48f66c5f4602961aa9cb6dd1a995d8969d6b39ce732f3 *libcxxabi_headers.zip
78a9606190fb227460ddcd276ad540873595d6a82fd1007f42900f53347e3eb1 *mksnapshot-v34.2.0-darwin-arm64.zip
6d4587a36f509356a908b6752de527cfe8a294a2d435b82ea3a98288c3a3a178 *mksnapshot-v34.2.0-darwin-x64.zip
17737bd34f7feefc5143b745f2c4f0e5a41678780e144eb43de41f8d4b9d7852 *mksnapshot-v34.2.0-linux-arm64-x64.zip
565aa9a84d913b7b5eb8d3b868cff151cb8a6c16548233ffd92b2f9bf3789227 *mksnapshot-v34.2.0-linux-armv7l-x64.zip
7332d7864ab4e5ee7fa8d00580d9357f30d0e69d3d1434d67c61f79f666314a4 *mksnapshot-v34.2.0-linux-x64.zip
cc81bfc9894378a9fc8a777429e04cc80860663b3dff8eba1f8cc72559a4f23d *mksnapshot-v34.2.0-mas-arm64.zip
c98230088698638159f6e7e0c5ddde3cb4dba9fa81d76e3c58fc86f96b63ef81 *mksnapshot-v34.2.0-mas-x64.zip
cc7b42943d998e1083ad8119dc2201dd27d709c15aa2a9b78f409485d2927592 *mksnapshot-v34.2.0-win32-arm64-x64.zip
ade9e3126f113318e226f9bdeba068b4383b30a98517a361386eff448459effa *mksnapshot-v34.2.0-win32-ia32.zip
344292ea318dc0e21f37bc7c82d57a57449f0fb278d9868f1b1b597bdcb1f36f *mksnapshot-v34.2.0-win32-x64.zip
c9b82c9f381742e839fea00aeb14f24519bcaf38a0f4eed25532191701f9535b *chromedriver-v34.3.2-darwin-arm64.zip
d556c1e2b06f1bf131e83c2fb981de755c28e1083a884d257eb964815be16b0c *chromedriver-v34.3.2-darwin-x64.zip
1cabad4f3303ac2ff172a9f22185f64944dbaa6fc68271609077158eaefdee35 *chromedriver-v34.3.2-linux-arm64.zip
4213ce52c72ef414179b5c5c22ae8423847ff030d438296bd6c2aac763930a7b *chromedriver-v34.3.2-linux-armv7l.zip
3c64c08221fdfc0f4be60ea8b1b126f2ecca45f60001b63778522f711022c6ea *chromedriver-v34.3.2-linux-x64.zip
e8388734d88e011cb6cd79795431de9206820749219d80565ee49d90501d2bf3 *chromedriver-v34.3.2-mas-arm64.zip
3ad1dd37bd6e0bb37e8503898db7aedd56bd5213e6d6760b05c3d11f4625062b *chromedriver-v34.3.2-mas-x64.zip
d567b481a0f5d88e84bba7718f89fb08f56363bfc4cb5914e1c2086358a5c252 *chromedriver-v34.3.2-win32-arm64.zip
df6732e9dc61cb20a3c0b2a2de453aac7e2bd54e7cbff43512afa614852c15fa *chromedriver-v34.3.2-win32-ia32.zip
dda0765c8d064924632e18cd152014ecd767f3808fc51c8249a053bfb7ca70a2 *chromedriver-v34.3.2-win32-x64.zip
1945f15caff98f2e0f1ee539c483d352fb8d4d0c13f342caa7abe247676d828c *electron-api.json
c078bbf727b3c3026f60e07a0f4643b85c06c581b54be017d0a6c284ba6772d3 *electron-v34.3.2-darwin-arm64-dsym-snapshot.zip
35f587754d6a3272606258386bf73688d63dd53c7e572d3a7cbaae6f3f60bdae *electron-v34.3.2-darwin-arm64-dsym.zip
08b14ee02c98353de3c738120dfd017322666e82b914a7f6de9b9888dcc5c0f0 *electron-v34.3.2-darwin-arm64-symbols.zip
2a4aa7e8fa30f229e465ebd18d3e4722e2b41529dc51a68a954d333a7e556ffe *electron-v34.3.2-darwin-arm64.zip
1509ccdeb80024f5e3edd5ecf804b4cef4e47ea2bd74e33ef0b39044b0ccf892 *electron-v34.3.2-darwin-x64-dsym-snapshot.zip
3bbe5d587c3f582ed8c126b0fb635cc02ad9a14d077b04892fe6f862092445b0 *electron-v34.3.2-darwin-x64-dsym.zip
fa7ece82e6ecaf1c94ed341e8ebff98e64687c68fe113f52cd9a21400302e22f *electron-v34.3.2-darwin-x64-symbols.zip
23938c62257a65a863ed7aa7c7966ba5f257a7d3dc16c78293e826962cc39c5c *electron-v34.3.2-darwin-x64.zip
0547eecf8ab538d74fa854f591ce8b888a3dbb339256d2db3038e7bb2c6dd929 *electron-v34.3.2-linux-arm64-debug.zip
676d0dc2b1c1c85c8b2abbb8cd5376ee22ecdb910493b910d9ae5a998532136a *electron-v34.3.2-linux-arm64-symbols.zip
774e4ccb39d553e5487994a9f8c60774a90f08cdb049ff65f3963fc27c969ff2 *electron-v34.3.2-linux-arm64.zip
0547eecf8ab538d74fa854f591ce8b888a3dbb339256d2db3038e7bb2c6dd929 *electron-v34.3.2-linux-armv7l-debug.zip
ba33bf53fcb35dea568a2795f5b23ecf46c218abe8258946611c72a1f42f716c *electron-v34.3.2-linux-armv7l-symbols.zip
73ae92c8fffb351d3a455569cf57ce9a3f676f42bf61939c613c607fe0fc3bfb *electron-v34.3.2-linux-armv7l.zip
e61a9a69dd7ea6f2687708a8e83516670cdea53c728226e598e2f6f1fad5b77b *electron-v34.3.2-linux-x64-debug.zip
f1a04df7fe67dd1cd29e7b87871525458d2eb24c0cf3b5835a1c56974707562a *electron-v34.3.2-linux-x64-symbols.zip
7b74c0c4fae82e27c7e9cbca13e9763e046113dba8737d3e27de9a0b300ac87e *electron-v34.3.2-linux-x64.zip
8571a6aa83e00925ceb39fdc5a45a9f6b9aa3d92fd84951c6f252ed723aea4ae *electron-v34.3.2-mas-arm64-dsym-snapshot.zip
477410c6f9a6c5eeaedf376058a02c2996fc0a334aa40eeec7d3734c09347f4d *electron-v34.3.2-mas-arm64-dsym.zip
c2e62dcd6630cb51b2d8e2869e74e47d29bda785521cea6e82e546d0fc58aabb *electron-v34.3.2-mas-arm64-symbols.zip
a1698e8546a062fd59b7f8e5507a7f3220fb00b347f2377de83fc9a07f7f3507 *electron-v34.3.2-mas-arm64.zip
741a24ac230a3651dca81d211f9f00b835c428a5ed0c5f67d370d4e88b62f8d6 *electron-v34.3.2-mas-x64-dsym-snapshot.zip
aeff97ec9e5c9e173ac89e38acd94476025c5640d5f27be1e8c2abd54398bab3 *electron-v34.3.2-mas-x64-dsym.zip
9f14b66b1d612ac66697288e8763171c388f7f200854871a5f0ab464a6a921c2 *electron-v34.3.2-mas-x64-symbols.zip
c979d7e7175f1e8e03ca187997d4c156b878189fc3611b347fadebcb16f3e027 *electron-v34.3.2-mas-x64.zip
f43c700641e8220205dd356952e32718d113cf530520c4ed7209b59851eac266 *electron-v34.3.2-win32-arm64-pdb.zip
3ba6e01c99bffac6b5dd3fd6f122ecdb571cf6f675dc5498c65050bd7a382ef8 *electron-v34.3.2-win32-arm64-symbols.zip
c23f84aabb09c24cd2ae759a547fdba4206af19a3bb0f4554a91cd9528648ad0 *electron-v34.3.2-win32-arm64-toolchain-profile.zip
9b9cb65d75a16782088b492f9ef3bb4d27525012b819c12bf29bd27e159d749b *electron-v34.3.2-win32-arm64.zip
1006e7af4c149114b5ebc3497617aaa6cd1bb0b131e0a225fd73709ff308f9c5 *electron-v34.3.2-win32-ia32-pdb.zip
1ecb6430cd04454f08f557c9579163f3552144bfcc0b67b768dad8868b5b891d *electron-v34.3.2-win32-ia32-symbols.zip
c23f84aabb09c24cd2ae759a547fdba4206af19a3bb0f4554a91cd9528648ad0 *electron-v34.3.2-win32-ia32-toolchain-profile.zip
d004fd5f853754001fafaec33e383d1950b30c935ee71b297ec1c9e084355e9b *electron-v34.3.2-win32-ia32.zip
4e0721552fd2f09e9466e88089af8b965f1bfbc4ae00a59aaf6245b1d1efabfd *electron-v34.3.2-win32-x64-pdb.zip
9dea812a7e7cd0fb18e5fed9a99db5531959a068c24d3c0ecedceb644cd3ffa0 *electron-v34.3.2-win32-x64-symbols.zip
c23f84aabb09c24cd2ae759a547fdba4206af19a3bb0f4554a91cd9528648ad0 *electron-v34.3.2-win32-x64-toolchain-profile.zip
1785e161420fb90d2331c26e50bba3413cae9625b7db3c8524ea02ade631efba *electron-v34.3.2-win32-x64.zip
722b304c31ddac58b0083d94a241c5276464f04bd8ea4fcbfd33051d197be103 *electron.d.ts
31ce159b2e47d1de5bc907d8e1c89726b0f2ba530ec2e9d7a8e5c723b1ccf6e0 *ffmpeg-v34.3.2-darwin-arm64.zip
565539bac64a6ee9cf6f188070f520210a1507341718f5dc388ac7c454b1e1d5 *ffmpeg-v34.3.2-darwin-x64.zip
6006ea0f46ab229feb2685be086b0fafd65981e2939dd2218a078459c75ab527 *ffmpeg-v34.3.2-linux-arm64.zip
9404ce2e85df7c40f809f2cf62c7af607de299839fe6b7ae978c3015500abcc8 *ffmpeg-v34.3.2-linux-armv7l.zip
79aec96898b7e2462826780ee0b52b9ab299dc662af333e128a34fd5ddae87f1 *ffmpeg-v34.3.2-linux-x64.zip
9190743c78210574faf5d5ecb82a00f8fa15e5f2253378cb925a99ca9d39961b *ffmpeg-v34.3.2-mas-arm64.zip
48915adcb1a6342efeda896035101300f0432c0304cfb38f2378e98c6309ebae *ffmpeg-v34.3.2-mas-x64.zip
745d5ef786de6d4a720475079836e2fda7b501cfcd255819485a47de5b24b74e *ffmpeg-v34.3.2-win32-arm64.zip
d0d86d60978439dc8ae4a723d4e4c1f853891d596bfd84033440a232fa762e2f *ffmpeg-v34.3.2-win32-ia32.zip
4441539fd8c9cbe79880ff1bade9bdc0c3744c33d7409130af6404e57ee401ff *ffmpeg-v34.3.2-win32-x64.zip
39edd1eeefe881aa75af0e438204e0b1c6e6724e34fa5819109276331c0c2c9a *hunspell_dictionaries.zip
20dd417536e5f4ebc01f480221284c0673729c27b082bc04e2922f16cd571719 *libcxx-objects-v34.3.2-linux-arm64.zip
7e53c5779c04f895f8282c0450ec4a63074d15a0e910e41006cfea133d0288af *libcxx-objects-v34.3.2-linux-armv7l.zip
92e2283c924ab523ffec3ea22513beaab6417f7fc3d570f57d51a1e1ceb7f510 *libcxx-objects-v34.3.2-linux-x64.zip
9bf3c6e8ad68f523fe086fada4148dd04e5bb0b9290d104873e66f2584a5cf50 *libcxx_headers.zip
34e4b44f9c5e08b557a2caed55456ce7690abab910196a783a2a47b58d2b9ac9 *libcxxabi_headers.zip
11f67635e6659f9188198e4086c51b89890b61a22f6c17c99eff35595ee8f51d *mksnapshot-v34.3.2-darwin-arm64.zip
c0add9ef4ac27c73fa043d04b4c9635fd3fd9f5c16d7a03e961864ba05252813 *mksnapshot-v34.3.2-darwin-x64.zip
6262adf86a340d8d28059937b01ef1117b93212e945fddbceea5c18d7e7c64f0 *mksnapshot-v34.3.2-linux-arm64-x64.zip
f7db8ebe91a1cc8d24ef6aad12949a18d8e4975ac296e3e5e9ecd88c9bccb143 *mksnapshot-v34.3.2-linux-armv7l-x64.zip
6642038e86bda362980ff1c8973a404e2b02efdd87de9e35b650fc1e743833da *mksnapshot-v34.3.2-linux-x64.zip
15883bf8e8cd737c3682d1e719d7cbac92f50b525681aac324dca876861dfc7d *mksnapshot-v34.3.2-mas-arm64.zip
4da23a950bfcc377ef21c37d496017ab4c36da03f3b41049ac114042c42608ce *mksnapshot-v34.3.2-mas-x64.zip
fab59573d3c2f9bdf31146a1896d24ac0c51f736aad86d2f3c7ecef13c05a7fd *mksnapshot-v34.3.2-win32-arm64-x64.zip
66f25e07c6f8d5d2009577a129440255a3baf63c929a5b60b2e77cd52e46105b *mksnapshot-v34.3.2-win32-ia32.zip
8168bfbf61882cfac80aed1e71e364e1c7f2fccd11eac298e6abade8b46894ea *mksnapshot-v34.3.2-win32-x64.zip

View file

@ -54,9 +54,11 @@ async function main(buildDir) {
...defaultOpts,
// TODO(deepak1556): Incorrectly declared type in electron-osx-sign
ignore: (filePath) => {
const ext = path_1.default.extname(filePath);
return filePath.includes(gpuHelperAppName) ||
filePath.includes(rendererHelperAppName) ||
filePath.includes(pluginHelperAppName);
filePath.includes(pluginHelperAppName) ||
ext === '.asar' || ext === '.dat' || ext === '.gif' || ext === '.icns' || ext === '.ico' || ext === '.json' || ext === '.mp3' || ext === '.nib' || ext === '.pak' || ext === '.png' || ext === '.scpt' || ext === '.ttf' || ext === '.wasm' || ext === '.woff' || ext === '.woff2';
}
};
const gpuHelperOpts = {

View file

@ -58,9 +58,11 @@ async function main(buildDir?: string): Promise<void> {
...defaultOpts,
// TODO(deepak1556): Incorrectly declared type in electron-osx-sign
ignore: (filePath: string) => {
const ext = path.extname(filePath);
return filePath.includes(gpuHelperAppName) ||
filePath.includes(rendererHelperAppName) ||
filePath.includes(pluginHelperAppName);
filePath.includes(pluginHelperAppName) ||
ext === '.asar' || ext === '.dat' || ext === '.gif' || ext === '.icns' || ext === '.ico' || ext === '.json' || ext === '.mp3' || ext === '.nib' || ext === '.pak' || ext === '.png' || ext === '.scpt' || ext === '.ttf' || ext === '.wasm' || ext === '.woff' || ext === '.woff2';
}
};

View file

@ -154,9 +154,20 @@ function getNodeChecksum(expectedName) {
}
function extractAlpinefromDocker(nodeVersion, platform, arch) {
const imageName = arch === 'arm64' ? 'arm64v8/node' : 'node';
let imageName = 'node';
let dockerPlatform = '';
if (arch === 'arm64') {
imageName = 'arm64v8/node';
const architecture = cp.execSync(`docker info --format '{{json .Architecture}}'`, { encoding: 'utf8' }).trim();
if (architecture !== '"aarch64"') {
dockerPlatform = '--platform=linux/arm64';
}
}
log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from docker image ${imageName}`);
const contents = cp.execSync(`docker run --rm ${imageName}:${nodeVersion}-alpine /bin/sh -c 'cat \`which node\`'`, { maxBuffer: 100 * 1024 * 1024, encoding: 'buffer' });
const contents = cp.execSync(`docker run --rm ${dockerPlatform} ${imageName}:${nodeVersion}-alpine /bin/sh -c 'cat \`which node\`'`, { maxBuffer: 100 * 1024 * 1024, encoding: 'buffer' });
return es.readArray([new File({ path: 'node', contents, stat: { mode: parseInt('755', 8) } })]);
}
@ -308,10 +319,11 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa
}
const name = product.nameShort;
const release = packageJson.release;
let packageJsonContents;
const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' })
.pipe(json({ name, version, dependencies: undefined, optionalDependencies: undefined, type: 'module' }))
.pipe(json({ name, version, release, dependencies: undefined, optionalDependencies: undefined, type: 'module' }))
.pipe(es.through(function (file) {
packageJsonContents = file.contents.toString();
this.emit('data', file);
@ -319,7 +331,7 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa
let productJsonContents;
const productJsonStream = gulp.src(['product.json'], { base: '.' })
.pipe(json({ commit, date: readISODate('out-build'), version }))
.pipe(json({ commit, date: readISODate('out-build'), version, release }))
.pipe(es.through(function (file) {
productJsonContents = file.contents.toString();
this.emit('data', file);

View file

@ -267,7 +267,8 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
}
const name = product.nameShort;
const packageJsonUpdates = { name, version };
const release = packageJson.release;
const packageJsonUpdates = { name, version, release };
if (platform === 'linux') {
packageJsonUpdates.desktopName = `${product.applicationName}.desktop`;
@ -284,7 +285,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
// Void - this is important, creates the product.json in .app
let productJsonContents;
const productJsonStream = gulp.src(['product.json'], { base: '.' })
.pipe(json({ commit, date: readISODate('out-build'), checksums, version }))
.pipe(json({ commit, date: readISODate('out-build'), checksums, version, release }))
.pipe(es.through(function (file) {
productJsonContents = file.contents.toString();
this.emit('data', file);
@ -372,7 +373,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
} else if (platform === 'darwin') {
const shortcut = gulp.src('resources/darwin/bin/code.sh')
.pipe(replace('@@APPNAME@@', product.applicationName))
.pipe(rename('bin/code'));
.pipe(rename('bin/' + product.applicationName));
all = es.merge(all, shortcut);
}

View file

@ -25,7 +25,6 @@ const exec = util.promisify(cp.exec);
const root = path.dirname(__dirname);
const commit = getVersion(root);
const linuxPackageRevision = Math.floor(new Date().getTime() / 1000);
/**
* @param {string} arch
@ -87,7 +86,7 @@ function prepareDebPackage(arch) {
const dependencies = await dependenciesGenerator.getDependencies('deb', binaryDir, product.applicationName, debArch);
gulp.src('resources/linux/debian/control.template', { base: '.' })
.pipe(replace('@@NAME@@', product.applicationName))
.pipe(replace('@@VERSION@@', packageJson.version + '-' + linuxPackageRevision))
.pipe(replace('@@VERSION@@', `${packageJson.version}.${packageJson.release}`))
.pipe(replace('@@ARCHITECTURE@@', debArch))
.pipe(replace('@@DEPENDS@@', dependencies.join(', ')))
.pipe(replace('@@RECOMMENDS@@', debianRecommendedDependencies.join(', ')))
@ -202,8 +201,7 @@ function prepareRpmPackage(arch) {
.pipe(replace('@@NAME@@', product.applicationName))
.pipe(replace('@@NAME_LONG@@', product.nameLong))
.pipe(replace('@@ICON@@', product.linuxIconName))
.pipe(replace('@@VERSION@@', packageJson.version))
.pipe(replace('@@RELEASE@@', linuxPackageRevision))
.pipe(replace('@@VERSION@@', `${packageJson.version}.${packageJson.release}`))
.pipe(replace('@@ARCHITECTURE@@', rpmArch))
.pipe(replace('@@LICENSE@@', product.licenseName))
.pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@'))
@ -279,7 +277,7 @@ function prepareSnapPackage(arch) {
const snapcraft = gulp.src('resources/linux/snap/snapcraft.yaml', { base: '.' })
.pipe(replace('@@NAME@@', product.applicationName))
.pipe(replace('@@VERSION@@', commit.substr(0, 8)))
.pipe(replace('@@VERSION@@', `${packageJson.version}.${packageJson.release}`))
// Possible run-on values https://snapcraft.io/docs/architectures
.pipe(replace('@@ARCHITECTURE@@', arch === 'x64' ? 'amd64' : arch))
.pipe(rename('snap/snapcraft.yaml'));

View file

@ -87,8 +87,8 @@ function buildWin32Setup(arch, target) {
NameLong: product.nameLong,
NameShort: product.nameShort,
DirName: product.win32DirName,
Version: pkg.version,
RawVersion: pkg.version.replace(/-\w+$/, ''),
Version: `${pkg.version}.${pkg.release}`,
RawVersion: `${pkg.version.replace(/-\w+$/, '')}.${pkg.release}`,
NameVersion: product.win32NameVersion + (target === 'user' ? ' (User)' : ''),
ExeBasename: product.nameShort,
RegValueName: product.win32RegValueName,

View file

@ -85,10 +85,7 @@ function getExtensionDownloadStream(extension) {
if (extension.vsix) {
input = ext.fromVsix(path_1.default.join(root, extension.vsix), extension);
}
else if (productjson.extensionsGallery?.serviceUrl) {
input = ext.fromMarketplace(productjson.extensionsGallery.serviceUrl, extension);
}
else {
else { // Void - ext-from-gh.patch
input = ext.fromGithub(extension);
}
return input.pipe((0, gulp_rename_1.default)(p => p.dirname = `${extension.name}/${p.dirname}`));

View file

@ -73,9 +73,7 @@ function getExtensionDownloadStream(extension: IExtensionDefinition) {
if (extension.vsix) {
input = ext.fromVsix(path.join(root, extension.vsix), extension);
} else if (productjson.extensionsGallery?.serviceUrl) {
input = ext.fromMarketplace(productjson.extensionsGallery.serviceUrl, extension);
} else {
} else { // Void - ext-from-gh.patch
input = ext.fromGithub(extension);
}

View file

@ -37,7 +37,7 @@ function getMangledFileContents(projectPath) {
* @type {webpack.LoaderDefinitionFunction}
*/
module.exports = async function (source, sourceMap, meta) {
if (this.mode !== 'production') {
if (true) { // Void - extensions-disable-mangler
// Only enable mangling in production builds
return source;
}

View file

@ -71,7 +71,7 @@
"type": "string",
"description": "The URL from where the vscode server will be downloaded. You can use the following variables and they will be replaced dynamically:\n- ${quality}: vscode server quality, e.g. stable or insiders\n- ${version}: vscode server version, e.g. 1.69.0\n- ${commit}: vscode server release commit\n- ${arch}: vscode server arch, e.g. x64, armhf, arm64\n- ${release}: release number",
"scope": "application",
"default": "https://github.com/voideditor/${NAME_OF_REPO}/releases/download/${version}.${release}/void-server-${os}-${arch}-${version}.${release}.tar.gz"
"default": "https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz"
},
"remote.SSH.remotePlatform": {
"type": "object",

View file

@ -32,12 +32,14 @@ export async function getVSCodeServerConfig(): Promise<IServerConfig> {
const customServerBinaryName = vscode.workspace.getConfiguration('remote.SSH.experimental').get<string>('serverBinaryName', '');
return {
version: vscode.version.replace('-insider', ''),
// version: vscode.version.replace('-insider', ''),
commit: productJson.commit,
quality: productJson.quality,
release: productJson.release,
serverApplicationName: customServerBinaryName || productJson.serverApplicationName,
serverDataFolderName: productJson.serverDataFolderName,
serverDownloadUrlTemplate: productJson.serverDownloadUrlTemplate
serverDownloadUrlTemplate: productJson.serverDownloadUrlTemplate,
// Void changed this
version: productJson.voidVersion
};
}

View file

@ -39,7 +39,7 @@ export class ServerInstallError extends Error {
}
}
const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/void-updates-server/releases/download/test/void-server-${os}-${arch}.tar.gz';
const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz';
export async function installCodeServer(conn: SSHConnection, serverDownloadUrlTemplate: string | undefined, extensionIds: string[], envVariables: string[], platform: string | undefined, useSocketPath: boolean, logger: Log): Promise<ServerInstallResult> {
let shell = 'powershell';

21
package-lock.json generated
View file

@ -117,7 +117,7 @@
"cssnano": "^6.0.3",
"debounce": "^1.0.0",
"deemon": "^1.8.0",
"electron": "34.2.0",
"electron": "34.3.2",
"eslint": "^9.11.1",
"eslint-formatter-compact": "^8.40.0",
"eslint-plugin-header": "3.1.1",
@ -3587,6 +3587,13 @@
"tslib": "^2.8.0"
}
},
"node_modules/@swc/helpers/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
@ -8001,9 +8008,9 @@
"dev": true
},
"node_modules/electron": {
"version": "34.2.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-34.2.0.tgz",
"integrity": "sha512-SYwBJNeXBTm1q/ErybQMUBZAYqEreBUqBwTrNkw1rV4YatDZk5Aittpcus3PPeC4UoI/tqmJ946uG8AKHTd6CA==",
"version": "34.3.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-34.3.2.tgz",
"integrity": "sha512-n9tzmFexVLxipZXwMTY30H10f0X9k2OP0SkpSwL5VvnDZi0l/Hc+8CEArKkQPbbSf/IS7nxgc96gtTaR+XoSBg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@ -21443,9 +21450,9 @@
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"dev": true,
"license": "0BSD"
},

View file

@ -69,7 +69,7 @@
"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 typescript@next && tsc -p ./build/tsconfig.build.json"
},
},
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"@floating-ui/react": "^0.27.5",
@ -178,7 +178,7 @@
"cssnano": "^6.0.3",
"debounce": "^1.0.0",
"deemon": "^1.8.0",
"electron": "34.2.0",
"electron": "34.3.2",
"eslint": "^9.11.1",
"eslint-formatter-compact": "^8.40.0",
"eslint-plugin-header": "3.1.1",

View file

@ -1,7 +1,7 @@
{
"nameShort": "Void",
"nameLong": "Void",
"voidVersion": "1.0.2",
"voidVersion": "1.0.3",
"applicationName": "void",
"dataFolderName": ".void-editor",
"win32MutexName": "voideditor",

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 200 KiB

View file

@ -6,6 +6,7 @@
import * as path from 'path';
import * as fs from 'original-fs';
import * as os from 'os';
import { createRequire } from 'node:module';
import { performance } from 'perf_hooks';
import { configurePortable } from './bootstrap-node.js';
import { bootstrapESM } from './bootstrap-esm.js';
@ -22,6 +23,7 @@ import { INLSConfiguration } from './vs/nls.js';
import { NativeParsedArgs } from './vs/platform/environment/common/argv.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
perf.mark('code/didStartMain');
@ -110,6 +112,17 @@ protocol.registerSchemesAsPrivileged([
// Global app listeners
registerListeners();
function resolveUserProduct() {
const userProductPath = path.join(userDataPath, 'product.json');
try {
// Assign the product configuration to the global scope
const productJson = require(userProductPath);
// @ts-expect-error
globalThis._VSCODE_USER_PRODUCT_JSON = productJson;
} catch (ex) {
}
}
/**
* We can resolve the NLS configuration early if it is defined
* in argv.json before `app.ready` event. Otherwise we can only
@ -206,6 +219,7 @@ async function onReady() {
async function startup(codeCachePath: string | undefined, nlsConfig: INLSConfiguration): Promise<void> {
process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || '';
resolveUserProduct();
// Bootstrap ESM
await bootstrapESM();

View file

@ -56,7 +56,8 @@ export type ExtensionVirtualWorkspaceSupport = {
export interface IProductConfiguration {
readonly version: string;
readonly voidVersion?: string; // Void added this
readonly voidVersion: string; // Void added this
readonly release: string; // VSCodium added this
readonly date?: string;
readonly quality?: string;
readonly commit?: string;

View file

@ -230,7 +230,7 @@ export class DiagnosticsService implements IDiagnosticsService {
private formatEnvironment(info: IMainProcessDiagnostics): string {
const output: string[] = [];
output.push(`Version: ${this.productService.nameShort} ${this.productService.version} (${this.productService.commit || 'Commit unknown'}, ${this.productService.date || 'Date unknown'})`);
output.push(`Version: ${this.productService.nameShort} ${this.productService.version} ${this.productService.release || 'Release unknown'} (${this.productService.commit || 'Commit unknown'}, ${this.productService.date || 'Date unknown'})`);
output.push(`OS Version: ${osLib.type()} ${osLib.arch()} ${osLib.release()}`);
const cpus = osLib.cpus();
if (cpus && cpus.length > 0) {

View file

@ -52,7 +52,6 @@ import { IProductService } from '../../product/common/productService.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { isLinux } from '../../../base/common/platform.js';
export const INativeServerExtensionManagementService = refineServiceDecorator<IExtensionManagementService, INativeServerExtensionManagementService>(IExtensionManagementService);
@ -85,7 +84,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
@IDownloadService private downloadService: IDownloadService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IFileService private readonly fileService: IFileService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IProductService productService: IProductService,
@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,
@IUriIdentityService uriIdentityService: IUriIdentityService,
@ -324,8 +322,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
private async downloadExtension(extension: IGalleryExtension, operation: InstallOperation, verifySignature: boolean, clientTargetPlatform?: TargetPlatform): Promise<{ readonly location: URI; readonly verificationStatus: ExtensionSignatureVerificationCode | undefined }> {
if (verifySignature) {
const value = this.configurationService.getValue('extensions.verifySignature');
verifySignature = isBoolean(value) ? value : true;
verifySignature = false; // Void disable-signature-verification-patch
}
const { location, verificationStatus } = await this.extensionsDownloader.download(extension, operation, verifySignature, clientTargetPlatform);

View file

@ -404,7 +404,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
}
private async getShellCommandLink(): Promise<{ readonly source: string; readonly target: string }> {
const target = resolve(this.environmentMainService.appRoot, 'bin', 'code');
const target = resolve(this.environmentMainService.appRoot, 'bin', this.productService.applicationName);
const source = `/usr/local/bin/${this.productService.applicationName}`;
// Ensure source exists
@ -639,7 +639,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
// macOS
if (this.environmentMainService.isBuilt) {
return join(this.environmentMainService.appRoot, 'bin', 'code');
return join(this.environmentMainService.appRoot, 'bin', `${this.productService.applicationName}`);
}
return join(this.environmentMainService.appRoot, 'scripts', 'code-cli.sh');

View file

@ -43,10 +43,11 @@ else if (globalThis._VSCODE_PRODUCT_JSON && globalThis._VSCODE_PACKAGE_JSON) {
// want to have it running out of sources so we
// read it from package.json only when we need it.
if (!product.version) {
const pkg = globalThis._VSCODE_PACKAGE_JSON as { version: string };
const pkg = globalThis._VSCODE_PACKAGE_JSON as { version: string, release: string };
Object.assign(product, {
version: pkg.version
version: pkg.version,
release: pkg.release
});
}
}

View file

@ -49,7 +49,8 @@ export const enum StateType {
export const enum UpdateType {
Setup,
Archive,
Snap
Snap,
WindowsInstaller
}
export const enum DisablementReason {
@ -108,3 +109,41 @@ export interface IUpdateService {
isLatestVersion(): Promise<boolean | undefined>;
_applySpecificUpdate(packagePath: string): Promise<void>;
}
export type Architecture =
| 'arm'
| 'arm64'
| 'ia32'
| 'loong64'
| 'mips'
| 'mipsel'
| 'ppc'
| 'ppc64'
| 'riscv64'
| 's390'
| 's390x'
| 'x64';
export type Platform =
| 'aix'
| 'android'
| 'darwin'
| 'freebsd'
| 'haiku'
| 'linux'
| 'openbsd'
| 'sunos'
| 'win32'
| 'cygwin'
| 'netbsd';
export type Quality =
| 'insider'
| 'stable';
export type Target =
| 'archive'
| 'msi'
| 'system'
| 'user';

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
// import { timeout } from '../../../base/common/async.js';
import { timeout } from '../../../base/common/async.js';
import { CancellationToken } from '../../../base/common/cancellation.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
@ -13,13 +14,16 @@ import { ILifecycleMainService, LifecycleMainPhase } from '../../lifecycle/elect
import { ILogService } from '../../log/common/log.js';
import { IProductService } from '../../product/common/productService.js';
import { IRequestService } from '../../request/common/request.js';
import { AvailableForDownload, DisablementReason, IUpdateService, State, StateType, UpdateType } from '../common/update.js';
import { Architecture, AvailableForDownload, DisablementReason, IUpdateService, Platform, State, StateType, Target, UpdateType } from '../common/update.js';
export function createUpdateURL(platform: string, quality: string, productService: IProductService): string {
// return `https://voideditor.dev/api/update/${platform}/stable`;
// return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`;
// https://github.com/VSCodium/update-api
return `https://updates.voideditor.dev/api/update/${platform}/${quality}/${productService.commit}`;
// Void - VSCodium's version-1-update.patch
export function createUpdateURL(productService: IProductService, quality: string, platform: Platform, architecture: Architecture, target?: Target): string { // return `https://voideditor.dev/api/update/${platform}/stable`;
if (target) {
return `${productService.updateUrl}/${quality}/${platform}/${architecture}/${target}/latest.json`;
} else { // we shouldn't usually have a target:
// https://raw.githubusercontent.com/voideditor/versions/refs/heads/main/stable/darwin/arm64/latest.json
return `${productService.updateUrl}/${quality}/${platform}/${architecture}/latest.json`;
}
}
export type UpdateErrorClassification = {
@ -81,22 +85,20 @@ export abstract class AbstractUpdateService implements IUpdateService {
return;
}
this.setState(State.Disabled(DisablementReason.ManuallyDisabled));
// Void - re-enabled auto updates
// this.setState(State.Disabled(DisablementReason.ManuallyDisabled));
// Void - temporarily disabled while we figure out how to do this the right way
// this.setState(State.Idle(this.getUpdateType()));
this.setState(State.Idle(this.getUpdateType()));
// start checking for updates after 10 seconds
// this.scheduleCheckForUpdates(10 * 1000).then(undefined, err => this.logService.error(err));
this.scheduleCheckForUpdates(10 * 1000).then(undefined, err => this.logService.error(err));
}
// private async scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
// await timeout(delay);
// await this.checkForUpdates(false);
// return await this.scheduleCheckForUpdates(60 * 60 * 1000);
// }
private async scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
await timeout(delay);
await this.checkForUpdates(false);
return await this.scheduleCheckForUpdates(60 * 60 * 1000);
}
async checkForUpdates(explicit: boolean): Promise<void> {
this.logService.trace('update#checkForUpdates, state = ', this.state.type);

View file

@ -13,11 +13,12 @@ import { IEnvironmentMainService } from '../../environment/electron-main/environ
import { ILifecycleMainService, IRelaunchHandler, IRelaunchOptions } from '../../lifecycle/electron-main/lifecycleMainService.js';
import { ILogService } from '../../log/common/log.js';
import { IProductService } from '../../product/common/productService.js';
import { IRequestService } from '../../request/common/request.js';
import { IRequestService, asJson } from '../../request/common/request.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
import { IUpdate, State, StateType, UpdateType } from '../common/update.js';
import { AbstractUpdateService, createUpdateURL, UpdateErrorClassification } from './abstractUpdateService.js';
import { CancellationToken } from '../../../base/common/cancellation.js';
import * as semver from 'semver';
export class DarwinUpdateService extends AbstractUpdateService implements IRelaunchHandler {
private readonly disposables = new DisposableStore();
@ -75,13 +76,7 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
// Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl
protected doBuildUpdateFeedUrl(quality: string): string | undefined {
let assetID: string;
if (!this.productService.darwinUniversalAssetId) {
assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64';
} else {
assetID = this.productService.darwinUniversalAssetId;
}
const url = createUpdateURL(assetID, quality, this.productService);
const url = createUpdateURL(this.productService, quality, process.platform, process.arch);
try {
electron.autoUpdater.setFeedURL({ url });
} catch (e) {
@ -93,8 +88,36 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
}
protected doCheckForUpdates(context: any): void {
if (!this.url) {
return;
}
this.setState(State.CheckingForUpdates(context));
electron.autoUpdater.checkForUpdates();
// electron.autoUpdater.checkForUpdates();
this.requestService.request({ url: this.url }, CancellationToken.None)
.then<IUpdate | null>(asJson)
.then(update => {
if (!update || !update.url || !update.version || !update.productVersion) {
this.setState(State.Idle(UpdateType.Setup));
return Promise.resolve(null);
}
const fetchedVersion = update.productVersion.replace(/(\d+\.\d+\.\d+)(?:\.(\d+))(\-\w+)?/, '$1$3+$2');
const currentVersion = `${this.productService.voidVersion}+${this.productService.release}`;
// Void compares voidVersion, not VSCode version
// const currentVersion = `${this.productService.version}+${this.productService.release}`;
if (semver.compareBuild(currentVersion, fetchedVersion) >= 0) {
this.setState(State.Idle(UpdateType.Setup));
}
else {
electron.autoUpdater.checkForUpdates();
}
return Promise.resolve(null);
})
}
private onUpdateAvailable(): void {

View file

@ -30,7 +30,7 @@ export class LinuxUpdateService extends AbstractUpdateService {
// Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl
protected doBuildUpdateFeedUrl(quality: string): string {
return createUpdateURL(`linux-${process.arch}`, quality, this.productService);
return createUpdateURL(this.productService, quality, process.platform, process.arch);
}
protected doCheckForUpdates(context: any): void {

View file

@ -9,7 +9,6 @@ import { tmpdir } from 'os';
import { timeout } from '../../../base/common/async.js';
import { CancellationToken } from '../../../base/common/cancellation.js';
import { memoize } from '../../../base/common/decorators.js';
import { hash } from '../../../base/common/hash.js';
import * as path from '../../../base/common/path.js';
import { URI } from '../../../base/common/uri.js';
import { checksum } from '../../../base/node/crypto.js';
@ -23,8 +22,9 @@ import { INativeHostMainService } from '../../native/electron-main/nativeHostMai
import { IProductService } from '../../product/common/productService.js';
import { asJson, IRequestService } from '../../request/common/request.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
import { AvailableForDownload, DisablementReason, IUpdate, State, StateType, UpdateType } from '../common/update.js';
import { AbstractUpdateService, createUpdateURL, UpdateErrorClassification } from './abstractUpdateService.js';
import { AvailableForDownload, DisablementReason, IUpdate, State, StateType, Target, UpdateType } from '../common/update.js';
import { AbstractUpdateService, createUpdateURL } from './abstractUpdateService.js';
import * as semver from 'semver';
async function pollUntil(fn: () => boolean, millis = 1000): Promise<void> {
while (!fn()) {
@ -40,9 +40,13 @@ interface IAvailableUpdate {
let _updateType: UpdateType | undefined = undefined;
function getUpdateType(): UpdateType {
if (typeof _updateType === 'undefined') {
_updateType = fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))
? UpdateType.Setup
: UpdateType.Archive;
if (fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))) {
_updateType = UpdateType.Setup;
} else if (path.basename(path.normalize(path.join(process.execPath, '..', '..'))) === 'Program Files') {
_updateType = UpdateType.WindowsInstaller;
} else {
_updateType = UpdateType.Archive;
}
}
return _updateType;
@ -61,6 +65,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
constructor(
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,
@IConfigurationService configurationService: IConfigurationService,
// @ts-expect-error
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IEnvironmentMainService environmentMainService: IEnvironmentMainService,
@IRequestService requestService: IRequestService,
@ -101,15 +106,24 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
// Void: buildUpdateFeedUrl -> doBuildUpdateFeedUrl
protected doBuildUpdateFeedUrl(quality: string): string | undefined {
let platform = `win32-${process.arch}`;
if (getUpdateType() === UpdateType.Archive) {
platform += '-archive';
} else if (this.productService.target === 'user') {
platform += '-user';
let target: Target;
switch (getUpdateType()) {
case UpdateType.Archive:
target = 'archive'
break;
case UpdateType.WindowsInstaller:
target = 'msi'
break;
default:
if (this.productService.target === 'user') {
target = 'user'
}
else {
target = 'system'
}
}
return createUpdateURL(platform, quality, this.productService);
return createUpdateURL(this.productService, quality, process.platform, process.arch, target);
}
protected doCheckForUpdates(context: any): void {
@ -129,6 +143,16 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
return Promise.resolve(null);
}
const fetchedVersion = update.productVersion.replace(/(\d+\.\d+\.\d+)(?:\.(\d+))(\-\w+)?/, '$1$3+$2');
const currentVersion = `${this.productService.voidVersion}+${this.productService.release}`;
// Void compares voidVersion, not VSCode version
// const currentVersion = `${this.productService.version}+${this.productService.release}`;
if (semver.compareBuild(currentVersion, fetchedVersion) >= 0) {
this.setState(State.Idle(updateType));
return Promise.resolve(null);
}
if (updateType === UpdateType.Archive) {
this.setState(State.AvailableForDownload(update));
return Promise.resolve(null);
@ -155,7 +179,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
this.availableUpdate = { packagePath };
this.setState(State.Downloaded(update));
const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates');
const fastUpdatesEnabled = getUpdateType() === UpdateType.Setup && this.configurationService.getValue('update.enableWindowsBackgroundUpdates');
if (fastUpdatesEnabled) {
if (this.productService.target === 'user') {
this.doApplyUpdate();
@ -167,7 +191,6 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
});
})
.then(undefined, err => {
this.telemetryService.publicLog2<{ messageHash: string }, UpdateErrorClassification>('update:error', { messageHash: String(hash(String(err))) });
this.logService.error(err);
// only show message when explicitly checking for updates
@ -251,10 +274,18 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
if (this.availableUpdate.updateFilePath) {
fs.unlinkSync(this.availableUpdate.updateFilePath);
} else {
spawn(this.availableUpdate.packagePath, ['/silent', '/log', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore']
});
const type = getUpdateType();
if (type === UpdateType.WindowsInstaller) {
spawn('msiexec.exe', ['/i', this.availableUpdate.packagePath], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore']
});
} else {
spawn(this.availableUpdate.packagePath, ['/silent', '/log', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore']
});
}
}
}

View file

@ -21,6 +21,7 @@ import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../editor/brows
import { defaultButtonStyles, defaultCheckboxStyles, defaultDialogStyles, defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';
import { ResultKind } from '../../../../platform/keybinding/common/keybindingResolver.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { getReleaseString } from '../../../../workbench/common/release.js';
export class BrowserDialogHandler extends AbstractDialogHandler {
@ -79,13 +80,14 @@ export class BrowserDialogHandler extends AbstractDialogHandler {
async about(): Promise<void> {
const detailString = (useAgo: boolean): string => {
const releaseString = getReleaseString();
return localize('aboutDetail',
"Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
this.productService.version || 'Unknown',
this.productService.commit || 'Unknown',
this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown',
navigator.userAgent
);
).replace('\n', `\n${releaseString} ${this.productService.release || 'Unknown'}\n`);
};
const detail = detailString(true);

View file

@ -0,0 +1,15 @@
// added by VSCodium
import { language } from '../../../vs/base/common/platform.js';
const DEFAULT_LABEL = 'Release:';
const LABELS: { [key: string]: string } = {
'en': DEFAULT_LABEL,
'fr': 'Révision :',
'ru': 'Релиз:',
'zh-hans': '发布版本:',
'zh-hant': '發布版本:',
};
export function getReleaseString(): string {
return LABELS[language] ?? DEFAULT_LABEL;
}

View file

@ -5,7 +5,7 @@
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
import { localize } from '../../../../nls.js';
import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';
import { IFileService } from '../../../../platform/files/common/files.js';
@ -15,7 +15,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
import { createSyncHeaders, IAuthenticationProvider, IResourceRefHandle } from '../../../../platform/userDataSync/common/userDataSync.js';
import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from '../../../services/authentication/common/authentication.js';
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsStorageService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService, SyncResource, EDIT_SESSIONS_PENDING_KEY } from '../common/editSessions.js';
import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsStorageService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService, SyncResource } from '../common/editSessions.js';
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
import { generateUuid } from '../../../../base/common/uuid.js';
import { getCurrentAuthenticationSessionInfo } from '../../../services/authentication/browser/authenticationService.js';
@ -91,7 +91,6 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
// If another window changes the preferred session storage, reset our cached auth state in memory
this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, this._store)(() => this.onDidChangeStorage()));
this.registerSignInAction();
this.registerResetAuthenticationAction();
this.signedInContext = EDIT_SESSIONS_SIGNED_IN.bindTo(this.contextKeyService);
@ -454,46 +453,6 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
}
}
private registerSignInAction() {
if (!this.serverConfiguration?.url) {
return;
}
const that = this;
const id = 'workbench.editSessions.actions.signIn';
const when = ContextKeyExpr.and(ContextKeyExpr.equals(EDIT_SESSIONS_PENDING_KEY, false), ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, false));
this._register(registerAction2(class ResetEditSessionAuthenticationAction extends Action2 {
constructor() {
super({
id,
title: localize('sign in', 'Turn on Cloud Changes...'),
category: EDIT_SESSION_SYNC_CATEGORY,
precondition: when,
menu: [{
id: MenuId.CommandPalette,
},
{
id: MenuId.AccountsContext,
group: '2_editSessions',
when,
}]
});
}
async run() {
return await that.initialize('write', false);
}
}));
this._register(MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
group: '2_editSessions',
command: {
id,
title: localize('sign in badge', 'Turn on Cloud Changes... (1)'),
},
when: ContextKeyExpr.and(ContextKeyExpr.equals(EDIT_SESSIONS_PENDING_KEY, true), ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, false))
}));
}
private registerResetAuthenticationAction() {
const that = this;
this._register(registerAction2(class ResetEditSessionAuthenticationAction extends Action2 {

View file

@ -0,0 +1,59 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
import { localize2 } from '../../../../nls.js';
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
export interface IDummyService {
readonly _serviceBrand: undefined;
}
export const IDummyService = createDecorator<IDummyService>('DummyService');
registerAction2(class extends Action2 {
constructor() {
super({
f1: true,
id: 'void.dummy',
title: localize2('dummy', 'dummy: Init'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyCode.Digit0,
weight: KeybindingWeight.VoidExtension,
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
console.log('hi')
const n = accessor.get(IDummyService)
console.log('Hi', n._serviceBrand)
}
})
// on mount
class DummyService extends Disposable implements IWorkbenchContribution, IDummyService {
static readonly ID = 'workbench.contrib.void.dummy'
_serviceBrand: undefined;
constructor(
) {
super()
}
}
registerSingleton(IDummyService, DummyService, InstantiationType.Eager);
registerWorkbenchContribution2(DummyService.ID, DummyService, WorkbenchPhase.BlockRestore);

View file

@ -34,7 +34,7 @@
// // const result = await new Promise((res, rej) => {
// // sendLLMMessage({
// // messages,
// // tools: ['grep_search'],
// // tools: ['search_files'],
// // onFinalMessage: ({ result: r, }) => {
// // res(r)
// // },
@ -73,7 +73,7 @@
// // const result = new Promise((res, rej) => {
// // sendLLMMessage({
// // messages,
// // tools: ['grep_search'],
// // tools: ['search_files'],
// // onResult: (r) => {
// // res(r)
// // }

View file

@ -793,7 +793,7 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
const featureName: FeatureName = 'Autocomplete'
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[modelSelection.providerName]?.[modelSelection.modelName] : undefined
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
// set parameters of `newAutocompletion` appropriately

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,317 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { URI } from '../../../../base/common/uri.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { IFileService } from '../../../../platform/files/common/files.js';
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
import { ShallowDirectoryItem, ToolCallParams, ToolResultType } from '../common/toolsServiceTypes.js';
import { MAX_CHILDREN_URIs_PAGE } from './toolsService.js';
import { IExplorerService } from '../../files/browser/files.js';
import { SortOrder } from '../../files/common/files.js';
import { ExplorerItem } from '../../files/common/explorerModel.js';
import { VoidDirectoryItem } from '../common/directoryStrTypes.js';
const MAX_CHARS_TOTAL_BEGINNING = 20_000
const MAX_CHARS_TOTAL_TOOL = 20_000
// const MAX_FILES_TOTAL = 200
export interface IDirectoryStrService {
readonly _serviceBrand: undefined;
getDirectoryStrTool(uri: URI): Promise<{ wasCutOff: boolean, str: string }>
getAllDirectoriesStr(): Promise<{ wasCutOff: boolean, str: string }>
}
export const IDirectoryStrService = createDecorator<IDirectoryStrService>('voidDirectoryStrService');
// Check if it's a known filtered type like .git
const shouldExcludeDirectory = (item: ExplorerItem) => {
if (item.name === '.git' ||
item.name === 'node_modules' ||
item.name.startsWith('.') ||
item.name === 'dist' ||
item.name === 'build' ||
item.name === 'out' ||
item.name === 'bin' ||
item.name === 'coverage' ||
item.name === '__pycache__' ||
item.name === 'env' ||
item.name === 'venv' ||
item.name === 'tmp' ||
item.name === 'temp' ||
item.name === 'artifacts' ||
item.name === 'target' ||
item.name === 'obj' ||
item.name === 'vendor' ||
item.name === 'logs' ||
item.name === 'cache'
) {
return true;
}
return false;
}
// ---------- ONE LAYER DEEP ----------
export const computeDirectoryTree1Deep = async (
fileService: IFileService,
rootURI: URI,
pageNumber: number = 1,
): Promise<ToolResultType['ls_dir']> => {
const stat = await fileService.resolve(rootURI, { resolveMetadata: false });
if (!stat.isDirectory) {
return { children: null, hasNextPage: false, hasPrevPage: false, itemsRemaining: 0 };
}
const nChildren = stat.children?.length ?? 0;
const fromChildIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1);
const toChildIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1; // INCLUSIVE
const listChildren = stat.children?.slice(fromChildIdx, toChildIdx + 1);
const children: ShallowDirectoryItem[] = listChildren?.map(child => ({
name: child.name,
uri: child.resource,
isDirectory: child.isDirectory,
isSymbolicLink: child.isSymbolicLink
})) ?? [];
const hasNextPage = (nChildren - 1) > toChildIdx;
const hasPrevPage = pageNumber > 1;
const itemsRemaining = Math.max(0, nChildren - (toChildIdx + 1));
return {
children,
hasNextPage,
hasPrevPage,
itemsRemaining
};
};
export const stringifyDirectoryTree1Deep = (params: ToolCallParams['ls_dir'], result: ToolResultType['ls_dir']): string => {
if (!result.children) {
return `Error: ${params.rootURI} is not a directory`;
}
let output = '';
const entries = result.children;
if (!result.hasPrevPage) { // is first page
output += `${params.rootURI.fsPath}\n`;
}
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
const isLast = i === entries.length - 1 && !result.hasNextPage;
const prefix = isLast ? '└── ' : '├── ';
output += `${prefix}${entry.name}${entry.isDirectory ? '/' : ''}${entry.isSymbolicLink ? ' (symbolic link)' : ''}\n`;
}
if (result.hasNextPage) {
output += `└── (${result.itemsRemaining} results remaining...)\n`;
}
return output;
};
// ---------- IN GENERAL ----------
// if the filter exists use it to filter out files and folders when creating the tree
const computeDirectoryTree = async (
eItem: ExplorerItem,
explorerService: IExplorerService
): Promise<VoidDirectoryItem> => {
// Fetch children with default sort order
const eChildren = await eItem.fetchChildren(SortOrder.FilesFirst);
const isGitIgnoredDirectory = eItem.isDirectory && shouldExcludeDirectory(eItem)
// Process children recursively
const children = !isGitIgnoredDirectory ? await Promise.all(
eChildren.map(async c => await computeDirectoryTree(c, explorerService))
) : null
// Create our directory item
const item: VoidDirectoryItem = {
uri: eItem.resource,
name: eItem.name,
isDirectory: eItem.isDirectory,
isSymbolicLink: eItem.isSymbolicLink,
children,
isGitIgnoredDirectory: isGitIgnoredDirectory && { numChildren: eItem.children.size },
};
return item;
};
const stringifyDirectoryTree = (
node: VoidDirectoryItem,
MAX_CHARS: number,
): { content: string, wasCutOff: boolean } => {
let content = '';
let wasCutOff = false;
// If we're already exceeding the max characters, return immediately
if (MAX_CHARS <= 0) {
return { content, wasCutOff: true };
}
// Add the root node first (without tree characters)
const nodeLine = `${node.name}${node.isDirectory ? '/' : ''}${node.isSymbolicLink ? ' (symbolic link)' : ''}\n`;
if (nodeLine.length > MAX_CHARS) {
return { content: '', wasCutOff: true };
}
content += nodeLine;
let remainingChars = MAX_CHARS - nodeLine.length;
// Then recursively add all children with proper tree formatting
if (node.children && node.children.length > 0) {
const { childrenContent, childrenCutOff } = renderChildren(
node.children,
remainingChars,
''
);
content += childrenContent;
wasCutOff = childrenCutOff;
}
return { content, wasCutOff };
};
// Helper function to render children with proper tree formatting
const renderChildren = (
children: VoidDirectoryItem[],
maxChars: number,
parentPrefix: string
): { childrenContent: string, childrenCutOff: boolean } => {
let childrenContent = '';
let childrenCutOff = false;
for (let i = 0; i < children.length; i++) {
const child = children[i];
const isLast = i === children.length - 1;
// Create the tree branch symbols
const branchSymbol = isLast ? '└── ' : '├── ';
const childLine = `${parentPrefix}${branchSymbol}${child.name}${child.isDirectory ? '/' : ''}${child.isSymbolicLink ? ' (symbolic link)' : ''}\n`;
// Check if adding this line would exceed the limit
if (childrenContent.length + childLine.length > maxChars) {
childrenCutOff = true;
break;
}
childrenContent += childLine;
const nextLevelPrefix = parentPrefix + (isLast ? ' ' : '│ ');
// if gitignored, just say the number of children
if (child.isDirectory && child.isGitIgnoredDirectory && child.isGitIgnoredDirectory.numChildren > 0) {
childrenContent += `${nextLevelPrefix}└── ... (${child.isGitIgnoredDirectory.numChildren} children) ...\n`
}
// Create the prefix for the next level (continuation line or space)
else if (child.children && child.children.length > 0) {
const {
childrenContent: grandChildrenContent,
childrenCutOff: grandChildrenCutOff
} = renderChildren(
child.children,
maxChars,
nextLevelPrefix
);
// If adding grandchildren content would exceed the limit
if (childrenContent.length + grandChildrenContent.length > maxChars) {
childrenCutOff = true;
break;
}
childrenContent += grandChildrenContent;
if (grandChildrenCutOff) {
childrenCutOff = true;
break;
}
}
}
return { childrenContent, childrenCutOff };
};
// ---------------------------------------------------
class DirectoryStrService extends Disposable implements IDirectoryStrService {
_serviceBrand: undefined;
constructor(
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IExplorerService private readonly explorerService: IExplorerService,
) {
super();
}
async getDirectoryStrTool(uri: URI) {
const eRoot = this.explorerService.findClosest(uri)
if (!eRoot) throw new Error(`There was a problem reading the URI: ${uri.fsPath}.`)
const dirTree = await computeDirectoryTree(eRoot, this.explorerService);
const { content, wasCutOff } = stringifyDirectoryTree(dirTree, MAX_CHARS_TOTAL_TOOL);
return {
str: `Directory of ${uri.fsPath}:\n${content}`,
wasCutOff,
}
}
async getAllDirectoriesStr() {
let str: string = '';
let cutOff = false;
const folders = this.workspaceContextService.getWorkspace().folders;
for (let i = 0; i < folders.length; i += 1) {
if (i > 0) str += '\n';
// this prioritizes filling 1st workspace before any other, etc
const f = folders[i];
str += `Directory of ${f.uri.fsPath}:\n`;
const rootURI = f.uri;
const eRoot = this.explorerService.findClosestRoot(rootURI);
if (!eRoot) continue;
// Use our new approach with direct explorer service
const dirTree = await computeDirectoryTree(eRoot, this.explorerService);
console.log('dirtree', dirTree)
const { content, wasCutOff } = stringifyDirectoryTree(dirTree, MAX_CHARS_TOTAL_BEGINNING - str.length);
str += content;
if (wasCutOff) {
cutOff = true;
break;
}
}
return { wasCutOff: cutOff, str };
}
}
registerSingleton(IDirectoryStrService, DirectoryStrService, InstantiationType.Delayed);

View file

@ -11,7 +11,7 @@ import { ICodeEditor, IOverlayWidget, IViewZone } from '../../../../editor/brows
// import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
// import { throttle } from '../../../../base/common/decorators.js';
import { ComputedDiff, findDiffs } from './helpers/findDiffs.js';
import { findDiffs } from './helpers/findDiffs.js';
import { EndOfLinePreference, IModelDecorationOptions, ITextModel } from '../../../../editor/common/model.js';
import { IRange } from '../../../../editor/common/core/range.js';
import { registerColor } from '../../../../platform/theme/common/colorUtils.js';
@ -40,13 +40,14 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
import { LLMChatMessage, OnError, errorDetails } from '../common/sendLLMMessageTypes.js';
import { IMetricsService } from '../common/metricsService.js';
import { IEditCodeService, AddCtrlKOpts, StartApplyingOpts } from './editCodeServiceInterface.js';
import { IEditCodeService, AddCtrlKOpts, StartApplyingOpts, CallBeforeStartApplyingOpts, } from './editCodeServiceInterface.js';
import { IVoidSettingsService } from '../common/voidSettingsService.js';
import { FeatureName } from '../common/voidSettingsTypes.js';
import { IVoidModelService } from '../common/voidModelService.js';
import { ITextFileService } from '../../../services/textfile/common/textfiles.js';
import { deepClone } from '../../../../base/common/objects.js';
import { acceptBg, acceptBorder, buttonFontSize, buttonTextColor, rejectBg, rejectBorder } from '../common/helpers/colors.js';
import { DiffArea, Diff, CtrlKZone, VoidFileSnapshot, DiffAreaSnapshotEntry, diffAreaSnapshotKeys, DiffZone, TrackingZone, ComputedDiff } from '../common/editCodeServiceTypes.js';
const configOfBG = (color: Color) => {
return { dark: color, light: color, hcDark: color, hcLight: color, }
@ -107,15 +108,31 @@ const getLeadingWhitespacePx = (editor: ICodeEditor, startLine: number): number
};
// Helper function to remove whitespace except newlines
const removeWhitespaceExceptNewlines = (str: string): string => {
return str.replace(/[^\S\n]+/g, '');
}
// finds block.orig in fileContents and return its range in file
// startingAtLine is 1-indexed and inclusive
const findTextInCode = (text: string, fileContents: string, startingAtLine?: number) => {
const idx = fileContents.indexOf(text,
startingAtLine !== undefined ?
fileContents.split('\n').slice(0, startingAtLine).join('\n').length // num characters in all lines before startingAtLine
: 0
)
const findTextInCode = (text: string, fileContents: string, canFallbackToRemoveWhitespace: boolean, startingAtLine?: number) => {
const startLineIdx = (fileContents: string) => startingAtLine !== undefined ?
fileContents.split('\n').slice(0, startingAtLine).join('\n').length // num characters in all lines before startingAtLine
: 0
// idx = starting index in fileContents
let idx = fileContents.indexOf(text, startLineIdx(fileContents))
// try to find it ignoring all whitespace this time
if (idx === -1 && canFallbackToRemoveWhitespace) {
text = removeWhitespaceExceptNewlines(text)
fileContents = removeWhitespaceExceptNewlines(fileContents)
idx = fileContents.indexOf(text, startLineIdx(fileContents));
}
if (idx === -1) return 'Not found' as const
const lastIdx = fileContents.lastIndexOf(text)
if (lastIdx !== idx) return 'Not unique' as const
@ -127,108 +144,6 @@ const findTextInCode = (text: string, fileContents: string, startingAtLine?: num
// // TODO diffArea should be removed if we just discovered it has no more diffs in it
// for (const diffareaid of this.diffAreasOfURI[uri.fsPath] || []) {
// const diffArea = this.diffAreaOfId[diffareaid]
// if (Object.keys(diffArea._diffOfId).length === 0 && !diffArea._sweepState.isStreaming) {
// const { onFinishEdit } = this._addToHistory(uri)
// this._deleteDiffArea(diffArea)
// onFinishEdit()
// }
// }
export type Diff = {
diffid: number;
diffareaid: number; // the diff area this diff belongs to, "computed"
} & ComputedDiff
// _ means anything we don't include if we clone it
// DiffArea.originalStartLine is the line in originalCode (not the file)
type CommonZoneProps = {
diffareaid: number;
startLine: number;
endLine: number;
_URI: URI; // typically we get the URI from model
}
type CtrlKZone = {
type: 'CtrlKZone';
originalCode?: undefined;
editorId: string; // the editor the input lives on
_mountInfo: null | {
textAreaRef: { current: HTMLTextAreaElement | null }
dispose: () => void;
refresh: () => void;
}
_linkedStreamingDiffZone: number | null; // diffareaid of the diffZone currently streaming here
_removeStylesFns: Set<Function> // these don't remove diffs or this diffArea, only their styles
} & CommonZoneProps
export type DiffZone = {
type: 'DiffZone',
originalCode: string;
_diffOfId: Record<string, Diff>; // diffid -> diff in this DiffArea
_streamState: {
isStreaming: true;
streamRequestIdRef: { current: string | null };
line: number;
} | {
isStreaming: false;
streamRequestIdRef?: undefined;
line?: undefined;
};
editorId?: undefined;
linkedStreamingDiffZone?: undefined;
_removeStylesFns: Set<Function> // these don't remove diffs or this diffArea, only their styles
} & CommonZoneProps
type TrackingZone<T> = {
type: 'TrackingZone';
metadata: T;
originalCode?: undefined;
editorId?: undefined;
_removeStylesFns?: undefined;
} & CommonZoneProps
// called DiffArea for historical purposes, we can rename to something like TextRegion if we want
export type DiffArea = CtrlKZone | DiffZone | TrackingZone<any>
const diffAreaSnapshotKeys = [
'type',
'diffareaid',
'originalCode',
'startLine',
'endLine',
'editorId',
] as const satisfies (keyof DiffArea)[]
type DiffAreaSnapshot<DiffAreaType extends DiffArea = DiffArea> = Pick<DiffAreaType, typeof diffAreaSnapshotKeys[number]>
type HistorySnapshot = {
snapshottedDiffAreaOfId: Record<string, DiffAreaSnapshot>;
entireFileCode: string;
}
// line/col is the location, originalCodeStartLine is the start line of the original code being displayed
type StreamLocationMutable = { line: number, col: number, addedSplitYet: boolean, originalCodeStartLine: number }
@ -243,23 +158,21 @@ class EditCodeService extends Disposable implements IEditCodeService {
diffAreaOfId: Record<string, DiffArea> = {}; // diffareaId -> diffArea
diffOfId: Record<string, Diff> = {}; // diffid -> diff (redundant with diffArea._diffOfId)
// events
// uri: diffZones // listen on change diffZones
private readonly _onDidAddOrDeleteDiffZones = new Emitter<{ uri: URI }>();
onDidAddOrDeleteDiffZones = this._onDidAddOrDeleteDiffZones.event;
// diffZone: [uri], diffs, isStreaming // listen on change diffs, change streaming (uri is const)
private readonly _onDidChangeDiffsInDiffZone = new Emitter<{ uri: URI, diffareaid: number }>();
private readonly _onDidChangeDiffsInDiffZoneNotStreaming = new Emitter<{ uri: URI, diffareaid: number }>();
private readonly _onDidChangeStreamingInDiffZone = new Emitter<{ uri: URI, diffareaid: number }>();
onDidChangeDiffsInDiffZone = this._onDidChangeDiffsInDiffZone.event;
onDidChangeDiffsInDiffZoneNotStreaming = this._onDidChangeDiffsInDiffZoneNotStreaming.event;
onDidChangeStreamingInDiffZone = this._onDidChangeStreamingInDiffZone.event;
// ctrlKZone: [uri], isStreaming // listen on change streaming
private readonly _onDidChangeStreamingInCtrlKZone = new Emitter<{ uri: URI; diffareaid: number }>();
onDidChangeStreamingInCtrlKZone = this._onDidChangeStreamingInCtrlKZone.event
onDidChangeStreamingInCtrlKZone = this._onDidChangeStreamingInCtrlKZone.event;
constructor(
@ -722,98 +635,98 @@ class EditCodeService extends Disposable implements IEditCodeService {
private _getCurrentVoidFileSnapshot = (uri: URI): VoidFileSnapshot => {
const { model } = this._voidModelService.getModel(uri)
const snapshottedDiffAreaOfId: Record<string, DiffAreaSnapshotEntry> = {}
for (const diffareaid in this.diffAreaOfId) {
const diffArea = this.diffAreaOfId[diffareaid]
if (diffArea._URI.fsPath !== uri.fsPath) continue
snapshottedDiffAreaOfId[diffareaid] = deepClone(
Object.fromEntries(diffAreaSnapshotKeys.map(key => [key, diffArea[key]]))
) as DiffAreaSnapshotEntry
}
const entireFileCode = model ? model.getValue(EndOfLinePreference.LF) : ''
// this._noLongerNeedModelReference(uri)
return {
snapshottedDiffAreaOfId,
entireFileCode, // the whole file's code
}
}
private _restoreVoidFileSnapshot = async (uri: URI, snapshot: VoidFileSnapshot) => {
// for each diffarea in this uri, stop streaming if currently streaming
for (const diffareaid in this.diffAreaOfId) {
const diffArea = this.diffAreaOfId[diffareaid]
if (diffArea.type === 'DiffZone')
this._stopIfStreaming(diffArea)
}
// delete all diffareas on this uri (clearing their styles)
this._deleteAllDiffAreas(uri)
const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = deepClone(snapshot) // don't want to destroy the snapshot
// restore diffAreaOfId and diffAreasOfModelId
for (const diffareaid in snapshottedDiffAreaOfId) {
const snapshottedDiffArea = snapshottedDiffAreaOfId[diffareaid]
if (snapshottedDiffArea.type === 'DiffZone') {
this.diffAreaOfId[diffareaid] = {
...snapshottedDiffArea as DiffAreaSnapshotEntry<DiffZone>,
type: 'DiffZone',
_diffOfId: {},
_URI: uri,
_streamState: { isStreaming: false }, // when restoring, we will never be streaming
_removeStylesFns: new Set(),
}
}
else if (snapshottedDiffArea.type === 'CtrlKZone') {
this.diffAreaOfId[diffareaid] = {
...snapshottedDiffArea as DiffAreaSnapshotEntry<CtrlKZone>,
_URI: uri,
_removeStylesFns: new Set<Function>(),
_mountInfo: null,
_linkedStreamingDiffZone: null, // when restoring, we will never be streaming
}
}
this._addOrInitializeDiffAreaAtURI(uri, diffareaid)
}
this._onDidAddOrDeleteDiffZones.fire({ uri })
// restore file content
this._writeURIText(uri, entireModelCode,
'wholeFileRange',
{ shouldRealignDiffAreas: false }
)
// this._noLongerNeedModelReference(uri)
}
private _addToHistory(uri: URI, opts?: { onWillUndo?: () => void }) {
const getCurrentSnapshot = (): HistorySnapshot => {
const { model } = this._voidModelService.getModel(uri)
const snapshottedDiffAreaOfId: Record<string, DiffAreaSnapshot> = {}
for (const diffareaid in this.diffAreaOfId) {
const diffArea = this.diffAreaOfId[diffareaid]
if (diffArea._URI.fsPath !== uri.fsPath) continue
snapshottedDiffAreaOfId[diffareaid] = deepClone(
Object.fromEntries(diffAreaSnapshotKeys.map(key => [key, diffArea[key]]))
) as DiffAreaSnapshot
}
const entireFileCode = model ? model.getValue(EndOfLinePreference.LF) : ''
// this._noLongerNeedModelReference(uri)
return {
snapshottedDiffAreaOfId,
entireFileCode, // the whole file's code
}
}
const restoreDiffAreas = async (snapshot: HistorySnapshot) => {
// for each diffarea in this uri, stop streaming if currently streaming
for (const diffareaid in this.diffAreaOfId) {
const diffArea = this.diffAreaOfId[diffareaid]
if (diffArea.type === 'DiffZone')
this._stopIfStreaming(diffArea)
}
// delete all diffareas on this uri (clearing their styles)
this._deleteAllDiffAreas(uri)
this.diffAreasOfURI[uri.fsPath]?.clear()
const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = deepClone(snapshot) // don't want to destroy the snapshot
// restore diffAreaOfId and diffAreasOfModelId
for (const diffareaid in snapshottedDiffAreaOfId) {
const snapshottedDiffArea = snapshottedDiffAreaOfId[diffareaid]
if (snapshottedDiffArea.type === 'DiffZone') {
this.diffAreaOfId[diffareaid] = {
...snapshottedDiffArea as DiffAreaSnapshot<DiffZone>,
type: 'DiffZone',
_diffOfId: {},
_URI: uri,
_streamState: { isStreaming: false }, // when restoring, we will never be streaming
_removeStylesFns: new Set(),
}
}
else if (snapshottedDiffArea.type === 'CtrlKZone') {
this.diffAreaOfId[diffareaid] = {
...snapshottedDiffArea as DiffAreaSnapshot<CtrlKZone>,
_URI: uri,
_removeStylesFns: new Set<Function>(),
_mountInfo: null,
_linkedStreamingDiffZone: null, // when restoring, we will never be streaming
}
}
this._addOrInitializeDiffAreaAtURI(uri, diffareaid)
}
this._onDidAddOrDeleteDiffZones.fire({ uri })
// restore file content
this._writeURIText(uri, entireModelCode,
'wholeFileRange',
{ shouldRealignDiffAreas: false }
)
// this._noLongerNeedModelReference(uri)
}
const beforeSnapshot: HistorySnapshot = getCurrentSnapshot()
let afterSnapshot: HistorySnapshot | null = null
const beforeSnapshot: VoidFileSnapshot = this._getCurrentVoidFileSnapshot(uri)
let afterSnapshot: VoidFileSnapshot | null = null
const elt: IUndoRedoElement = {
type: UndoRedoElementType.Resource,
resource: uri,
label: 'Void Agent',
code: 'undoredo.editCode',
undo: () => { opts?.onWillUndo?.(); restoreDiffAreas(beforeSnapshot); },
redo: () => { if (afterSnapshot) restoreDiffAreas(afterSnapshot) }
undo: () => { opts?.onWillUndo?.(); this._restoreVoidFileSnapshot(uri, beforeSnapshot); },
redo: () => { if (afterSnapshot) this._restoreVoidFileSnapshot(uri, afterSnapshot) }
}
this._undoRedoService.pushElement(elt)
const onFinishEdit = async () => {
afterSnapshot = getCurrentSnapshot()
afterSnapshot = this._getCurrentVoidFileSnapshot(uri)
await this._textFileService.save(uri, { // we want [our change] -> [save] so it's all treated as one change.
skipSaveParticipants: true // avoid triggering extensions etc (if they reformat the page, it will add another item to the undo stack)
})
@ -822,6 +735,16 @@ class EditCodeService extends Disposable implements IEditCodeService {
}
public getVoidFileSnapshot(uri: URI) {
return this._getCurrentVoidFileSnapshot(uri)
}
public restoreVoidFileSnapshot(uri: URI, snapshot: VoidFileSnapshot): void {
this._restoreVoidFileSnapshot(uri, snapshot)
}
// delete diffOfId and diffArea._diffOfId
private _deleteDiff(diff: Diff) {
const diffArea = this.diffAreaOfId[diff.diffareaid]
@ -886,6 +809,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
else if (diffArea.type === 'CtrlKZone')
this._deleteCtrlKZone(diffArea)
})
this.diffAreasOfURI[uri.fsPath]?.clear()
}
private _addOrInitializeDiffAreaAtURI = (uri: URI, diffareaid: string | number) => {
@ -994,7 +918,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
if (diffArea?.type !== 'DiffZone') continue
// fire changed diffs (this is the only place Diffs are added)
if (!diffArea._streamState.isStreaming) {
this._onDidChangeDiffsInDiffZone.fire({ uri, diffareaid: diffArea.diffareaid })
this._onDidChangeDiffsInDiffZoneNotStreaming.fire({ uri, diffareaid: diffArea.diffareaid })
}
}
}
@ -1160,29 +1084,50 @@ class EditCodeService extends Disposable implements IEditCodeService {
private _getURIBeforeStartApplying(opts: CallBeforeStartApplyingOpts) {
// SR
if (opts.from === 'ClickApply') {
const uri = this._uriOfGivenURI(opts.uri)
if (!uri) return
return uri
}
else if (opts.from === 'QuickEdit') {
const { diffareaid } = opts
const ctrlKZone = this.diffAreaOfId[diffareaid]
if (ctrlKZone?.type !== 'CtrlKZone') return
const { _URI: uri } = ctrlKZone
return uri
}
return
}
public async callBeforeStartApplying(opts: CallBeforeStartApplyingOpts) {
const uri = this._getURIBeforeStartApplying(opts)
if (!uri) return
await this._voidModelService.initializeModel(uri)
}
// the applyDonePromise this returns can reject, and should be caught with .catch
public async startApplying(opts: StartApplyingOpts): Promise<[URI, Promise<void>] | null> {
public startApplying(opts: StartApplyingOpts): [URI, Promise<void>] | null {
let res: [DiffZone, Promise<void>] | undefined = undefined
if (opts.from === 'QuickEdit') {
res = await this._initializeWriteoverStream(opts) // rewrite
res = this._initializeWriteoverStream(opts) // rewrite
}
else if (opts.from === 'ClickApply') {
if (this._settingsService.state.globalSettings.enableFastApply) {
const numCharsInFile = this._fileLengthOfGivenURI(opts.uri)
if (numCharsInFile === null) return null
if (numCharsInFile < 1000) { // slow apply for short files (especially important for empty files)
res = await this._initializeWriteoverStream(opts)
res = this._initializeWriteoverStream(opts)
}
else {
res = await this._initializeSearchAndReplaceStream(opts) // fast apply
res = this._initializeSearchAndReplaceStream(opts) // fast apply
}
}
else {
res = await this._initializeWriteoverStream(opts) // rewrite
res = this._initializeWriteoverStream(opts) // rewrite
}
}
@ -1278,6 +1223,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
_removeStylesFns: new Set(),
}
console.log('FIRING START STREAMING IN DIFFZONE!!!')
const diffZone = this._addDiffArea(adding)
this._onDidChangeStreamingInDiffZone.fire({ uri, diffareaid: diffZone.diffareaid })
this._onDidAddOrDeleteDiffZones.fire({ uri })
@ -1308,19 +1254,17 @@ class EditCodeService extends Disposable implements IEditCodeService {
}
private async _initializeWriteoverStream(opts: StartApplyingOpts): Promise<[DiffZone, Promise<void>] | undefined> {
private _initializeWriteoverStream(opts: StartApplyingOpts): [DiffZone, Promise<void>] | undefined {
const { from, } = opts
let uri: URI
let startRange: 'fullFile' | [number, number]
const uri = this._getURIBeforeStartApplying(opts)
if (!uri) return
let startRange: 'fullFile' | [number, number]
let ctrlKZoneIfQuickEdit: CtrlKZone | null = null
if (from === 'ClickApply') {
const uri_ = this._uriOfGivenURI(opts.uri)
if (!uri_) return
uri = uri_
startRange = 'fullFile'
}
else if (from === 'QuickEdit') {
@ -1328,15 +1272,13 @@ class EditCodeService extends Disposable implements IEditCodeService {
const ctrlKZone = this.diffAreaOfId[diffareaid]
if (ctrlKZone?.type !== 'CtrlKZone') return
ctrlKZoneIfQuickEdit = ctrlKZone
const { startLine: startLine_, endLine: endLine_, _URI } = ctrlKZone
uri = _URI
const { startLine: startLine_, endLine: endLine_ } = ctrlKZone
startRange = [startLine_, endLine_]
}
else {
throw new Error(`Void: diff.type not recognized on: ${from}`)
}
await this._voidModelService.initializeModel(uri)
const { model } = this._voidModelService.getModel(uri)
if (!model) return
@ -1434,7 +1376,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
const featureName: FeatureName = opts.from === 'ClickApply' ? 'Apply' : 'Ctrl+K'
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[modelSelection.providerName]?.[modelSelection.modelName] : undefined
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
// allowed to throw errors - this is called inside a promise that handles everything
const runWriteover = async () => {
@ -1530,13 +1472,12 @@ class EditCodeService extends Disposable implements IEditCodeService {
}
private async _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }): Promise<[DiffZone, Promise<void>] | undefined> {
const { from, applyStr, uri: givenURI, } = opts
private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }): [DiffZone, Promise<void>] | undefined {
const { from, applyStr, } = opts
const uri = this._uriOfGivenURI(givenURI)
const uri = this._getURIBeforeStartApplying(opts)
if (!uri) return
await this._voidModelService.initializeModel(uri)
const { model } = this._voidModelService.getModel(uri)
if (!model) return
@ -1643,7 +1584,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
const featureName: FeatureName = 'Apply'
const modelSelection = this._settingsService.state.modelSelectionOfFeature[featureName]
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[modelSelection.providerName]?.[modelSelection.modelName] : undefined
const modelSelectionOptions = modelSelection ? this._settingsService.state.optionsOfModelSelection[featureName][modelSelection.providerName]?.[modelSelection.modelName] : undefined
const N_RETRIES = 5
@ -1691,7 +1632,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
// update stream state to the first line of original if some portion of original has been written
if (shouldUpdateOrigStreamStyle && block.orig.trim().length >= 20) {
const startingAtLine = diffZone._streamState.line ?? 1 // dont go backwards if already have a stream line
const originalRange = findTextInCode(block.orig, originalFileCode, startingAtLine)
const originalRange = findTextInCode(block.orig, originalFileCode, false, startingAtLine)
if (typeof originalRange !== 'string') {
const [startLine, _] = convertOriginalRangeToFinalRange(originalRange)
diffZone._streamState.line = startLine
@ -1718,7 +1659,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
if (!(blockNum in addedTrackingZoneOfBlockNum)) {
const originalBounds = findTextInCode(block.orig, originalFileCode)
const originalBounds = findTextInCode(block.orig, originalFileCode, true)
// if error
if (typeof originalBounds === 'string') {
console.log('--------------Error finding text in code:')
@ -1757,14 +1698,15 @@ class EditCodeService extends Disposable implements IEditCodeService {
return
}
console.log('---------adding-------')
console.log('CURRENT TEXT!!!', { current: model?.getValue() })
console.log('block', deepClone(block))
console.log('origBounds', originalBounds)
const [startLine, endLine] = convertOriginalRangeToFinalRange(originalBounds)
console.log('start end', startLine, endLine)
// console.log('---------adding-------')
// console.log('CURRENT TEXT!!!', { current: model?.getValue() })
// console.log('block', deepClone(block))
// console.log('origBounds', originalBounds)
// console.log('start end', startLine, endLine)
// otherwise if no error, add the position as a diffarea
const adding: Omit<TrackingZone<SearchReplaceDiffAreaMetadata>, 'diffareaid'> = {
@ -1821,7 +1763,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
onFinalMessage: async (params) => {
const { fullText } = params
console.log('DONE - editCode!', { fullText })
// 1. wait 500ms and fix lint errors - call lint error workflow
// (update react state to say "Fixing errors")
@ -1836,10 +1777,11 @@ class EditCodeService extends Disposable implements IEditCodeService {
// IMPORTANT - sort by lineNum
addedTrackingZoneOfBlockNum.sort((a, b) => a.metadata.originalBounds[0] - b.metadata.originalBounds[0])
const { model } = this._voidModelService.getModel(uri)
console.log('CURRENT TEXT!!!', { current: model?.getValue() })
console.log('addedTrackingZoneOfBlockNum', addedTrackingZoneOfBlockNum)
console.log('blocks', deepClone(blocks))
// const { model } = this._voidModelService.getModel(uri)
// console.log('DONE - editCode!', { fullText })
// console.log('CURRENT TEXT!!!', { current: model?.getValue() })
// console.log('addedTrackingZoneOfBlockNum', addedTrackingZoneOfBlockNum)
// console.log('blocks', deepClone(blocks))
for (let blockNum = addedTrackingZoneOfBlockNum.length - 1; blockNum >= 0; blockNum -= 1) {
const { originalBounds } = addedTrackingZoneOfBlockNum[blockNum].metadata

View file

@ -7,13 +7,20 @@ import { Event } from '../../../../base/common/event.js';
import { URI } from '../../../../base/common/uri.js';
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { Diff, DiffArea } from './editCodeService.js';
import { Diff, DiffArea, VoidFileSnapshot } from '../common/editCodeServiceTypes.js';
export type StartBehavior = 'accept-conflicts' | 'reject-conflicts' | 'keep-conflicts'
export type StartApplyingOpts = ({
export type CallBeforeStartApplyingOpts = {
from: 'QuickEdit';
diffareaid: number; // id of the CtrlK area (contains text selection)
} | {
from: 'ClickApply';
uri: 'current' | URI;
}
export type StartApplyingOpts = {
from: 'QuickEdit';
diffareaid: number; // id of the CtrlK area (contains text selection)
startBehavior: StartBehavior;
@ -22,9 +29,7 @@ export type StartApplyingOpts = ({
applyStr: string;
uri: 'current' | URI;
startBehavior: StartBehavior;
})
}
export type AddCtrlKOpts = {
startLine: number,
@ -37,7 +42,8 @@ export const IEditCodeService = createDecorator<IEditCodeService>('editCodeServi
export interface IEditCodeService {
readonly _serviceBrand: undefined;
startApplying(opts: StartApplyingOpts): Promise<[URI, Promise<void>] | null>;
callBeforeStartApplying(opts: CallBeforeStartApplyingOpts): Promise<void>;
startApplying(opts: StartApplyingOpts): [URI, Promise<void>] | null;
addCtrlKZone(opts: AddCtrlKOpts): number | undefined;
removeCtrlKZone(opts: { diffareaid: number }): void;
@ -49,7 +55,7 @@ export interface IEditCodeService {
// events
onDidAddOrDeleteDiffZones: Event<{ uri: URI }>;
onDidChangeDiffsInDiffZone: Event<{ uri: URI; diffareaid: number }>; // only fires when not streaming!!! streaming would be too much
onDidChangeDiffsInDiffZoneNotStreaming: Event<{ uri: URI; diffareaid: number }>; // only fires when not streaming!!! streaming would be too much
onDidChangeStreamingInDiffZone: Event<{ uri: URI; diffareaid: number }>;
onDidChangeStreamingInCtrlKZone: Event<{ uri: URI; diffareaid: number }>;
@ -61,4 +67,6 @@ export interface IEditCodeService {
interruptURIStreaming(opts: { uri: URI }): void;
// testDiffs(): void;
getVoidFileSnapshot(uri: URI): VoidFileSnapshot;
restoreVoidFileSnapshot(uri: URI, snapshot: VoidFileSnapshot): void;
}

View file

@ -3,34 +3,9 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { ComputedDiff } from '../../common/editCodeServiceTypes.js';
import { diffLines } from '../react/out/diff/index.js'
export type ComputedDiff = {
type: 'edit';
originalCode: string;
originalStartLine: number;
originalEndLine: number;
code: string;
startLine: number; // 1-indexed
endLine: number;
} | {
type: 'insertion';
// originalCode: string;
originalStartLine: number; // insertion starts on column 0 of this
// originalEndLine: number;
code: string;
startLine: number;
endLine: number;
} | {
type: 'deletion';
originalCode: string;
originalStartLine: number;
originalEndLine: number;
// code: string;
startLine: number; // deletion starts on column 0 of this
// endLine: number;
}
export function findDiffs(oldStr: string, newStr: string) {
// this makes it so the end of the file always ends with a \n (if you don't have this, then diffing E vs E\n gives an "edit". With it, you end up diffing E\n vs E\n\n which now properly gives an insertion)

View file

@ -11,6 +11,7 @@ import * as dom from '../../../../base/browser/dom.js';
import { IMetricsService } from '../common/metricsService.js';
export interface IMetricsPollService {
readonly _serviceBrand: undefined;
}

View file

@ -1,12 +1,16 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { useState, useEffect, useCallback } from 'react'
import { useAccessor, useCommandBarState, useCommandBarURIListener, useSettingsState } from '../util/services.js'
import { usePromise, useRefState } from '../util/helpers.js'
import { isFeatureNameDisabled } from '../../../../common/voidSettingsTypes.js'
import { URI } from '../../../../../../../base/common/uri.js'
import { FileSymlink, LucideIcon, RotateCw } from 'lucide-react'
import { FileSymlink, LucideIcon, RotateCw, Terminal } from 'lucide-react'
import { Check, X, Square, Copy, Play, } from 'lucide-react'
import { getBasename, ListableToolItem, ToolChildrenWrapper } from '../sidebar-tsx/SidebarChat.js'
import { ChatMarkdownRender } from './ChatMarkdownRender.js'
enum CopyButtonText {
Idle = 'Copy',
@ -64,9 +68,9 @@ export const IconShell1 = ({ onClick, Icon, disabled, className }: IconButtonPro
// </button>
// )
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
const COPY_FEEDBACK_TIMEOUT = 1500 // amount of time to say 'Copied!'
const CopyButton = ({ codeStr }: { codeStr: string }) => {
export const CopyButton = ({ codeStr }: { codeStr: string }) => {
const accessor = useAccessor()
const metricsService = accessor.get('IMetricsService')
@ -94,11 +98,6 @@ const CopyButton = ({ codeStr }: { codeStr: string }) => {
}
// state persisted for duration of react only
// TODO change this to use type `ChatThreads.applyBoxState[applyBoxId]`
const applyingURIOfApplyBoxIdRef: { current: { [applyBoxId: string]: URI | undefined } } = { current: {} }
export const JumpToFileButton = ({ uri }: { uri: URI | 'current' }) => {
@ -113,176 +112,69 @@ export const JumpToFileButton = ({ uri }: { uri: URI | 'current' }) => {
}}
/>
)
return jumpToFileButton
}
export const useApplyButtonHTML = ({ codeStr, applyBoxId, uri }: { codeStr: string, applyBoxId: string, uri: URI | 'current' }) => {
export const JumpToTerminalButton = ({ onClick }: { onClick: () => void }) => {
return (
<IconShell1
Icon={Terminal}
onClick={onClick}
className="text-void-fg-1"
/>
)
}
// state persisted for duration of react only
// TODO change this to use type `ChatThreads.applyBoxState[applyBoxId]`
const applyingURIOfApplyBoxIdRef: { current: { [applyBoxId: string]: URI | undefined } } = { current: {} }
const getUriBeingApplied = (applyBoxId: string) => {
return applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null
}
export const useApplyButtonState = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' }) => {
const settingsState = useSettingsState()
const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId
const accessor = useAccessor()
const editCodeService = accessor.get('IEditCodeService')
const voidCommandBarService = accessor.get('IVoidCommandBarService')
const metricsService = accessor.get('IMetricsService')
const [_, rerender] = useState(0)
const getUriBeingApplied = useCallback(() => {
return applyingURIOfApplyBoxIdRef.current[applyBoxId] ?? null
}, [applyBoxId])
const getStreamState = useCallback(() => {
const uri = getUriBeingApplied()
const uri = getUriBeingApplied(applyBoxId)
if (!uri) return 'idle-no-changes'
return voidCommandBarService.getStreamState(uri)
}, [voidCommandBarService, getUriBeingApplied])
}, [voidCommandBarService, applyBoxId])
// listen for stream updates on this box
useCommandBarURIListener(useCallback((uri_) => {
const shouldUpdate = (
getUriBeingApplied()?.fsPath === uri_.fsPath
getUriBeingApplied(applyBoxId)?.fsPath === uri_.fsPath
|| (uri !== 'current' && uri.fsPath === uri_.fsPath)
)
if (!shouldUpdate) return
rerender(c => c + 1)
}, [applyBoxId, editCodeService, getUriBeingApplied, uri])
)
const onClickSubmit = useCallback(async () => {
if (isDisabled) return
if (getStreamState() === 'streaming') return
const [newApplyingUri, applyDonePromise] = await editCodeService.startApplying({
from: 'ClickApply',
applyStr: codeStr,
uri: uri,
startBehavior: 'keep-conflicts',
}) ?? []
// catch any errors by interrupting the stream
applyDonePromise?.catch(e => { if (newApplyingUri) editCodeService.interruptURIStreaming({ uri: newApplyingUri }) })
applyingURIOfApplyBoxIdRef.current[applyBoxId] = newApplyingUri ?? undefined
rerender(c => c + 1)
metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only
}, [isDisabled, getStreamState, editCodeService, codeStr, uri, applyBoxId, metricsService])
const onInterrupt = useCallback(() => {
if (getStreamState() !== 'streaming') return
const uri = getUriBeingApplied()
if (!uri) return
editCodeService.interruptURIStreaming({ uri })
metricsService.capture('Stop Apply', {})
}, [getStreamState, getUriBeingApplied, editCodeService, metricsService])
const onAccept = useCallback(() => {
const uri = getUriBeingApplied()
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false })
}, [getUriBeingApplied, editCodeService])
const onReject = useCallback(() => {
const uri = getUriBeingApplied()
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
}, [getUriBeingApplied, editCodeService])
const onReapply = useCallback(() => {
onReject()
onClickSubmit()
}, [onReject, onClickSubmit])
if (shouldUpdate) {
rerender(c => c + 1)
console.log('rerendering....')
}
}, [applyBoxId, applyBoxId, uri]))
const currStreamState = getStreamState()
const copyButton = (
<CopyButton codeStr={codeStr} />
)
const playButton = (
<IconShell1
Icon={Play}
onClick={onClickSubmit}
/>
)
const stopButton = (
<IconShell1
Icon={Square}
onClick={onInterrupt}
/>
)
const reapplyButton = (
<IconShell1
Icon={RotateCw}
onClick={onReapply}
/>
)
const acceptButton = (
<IconShell1
Icon={Check}
onClick={onAccept}
className="text-green-600"
/>
)
const rejectButton = (
<IconShell1
Icon={X}
onClick={onReject}
className="text-red-600"
/>
)
let buttonsHTML = <></>
if (currStreamState === 'streaming') {
buttonsHTML = <>
<JumpToFileButton uri={uri} />
{copyButton}
{stopButton}
</>
}
if (currStreamState === 'idle-no-changes') {
buttonsHTML = <>
<JumpToFileButton uri={uri} />
{copyButton}
{playButton}
</>
}
if (currStreamState === 'idle-has-changes') {
buttonsHTML = <>
<JumpToFileButton uri={uri} />
{reapplyButton}
{rejectButton}
{acceptButton}
</>
}
const color = (
currStreamState === 'idle-no-changes' ? 'dark' :
currStreamState === 'streaming' ? 'orange' :
currStreamState === 'idle-has-changes' ? 'green' :
null
)
const statusIndicatorHTML = <StatusIndicator className='mx-2' color={color} />
return {
statusIndicatorHTML,
buttonsHTML,
getStreamState,
isDisabled,
currStreamState,
}
}
export const StatusIndicator = ({ color, title, className }: { color: 'green' | 'orange' | 'dark' | null, title?: React.ReactNode, className?: string }) => {
return (
<div className={`flex flex-row text-void-fg-3 text-xs items-center gap-1 ${className}`}>
@ -300,6 +192,109 @@ export const StatusIndicator = ({ color, title, className }: { color: 'green' |
);
};
export const StatusIndicatorHTML = ({ applyBoxId, uri }: { applyBoxId: string, uri: URI | 'current' }) => {
const { currStreamState } = useApplyButtonState({ applyBoxId, uri })
const color = (
currStreamState === 'idle-no-changes' ? 'dark' :
currStreamState === 'streaming' ? 'orange' :
currStreamState === 'idle-has-changes' ? 'green' :
null
)
const statusIndicatorHTML = <StatusIndicator className='mx-2' color={color} />
return statusIndicatorHTML
}
export const ApplyButtonsHTML = ({ codeStr, applyBoxId, reapplyIcon, uri }: { codeStr: string, applyBoxId: string, reapplyIcon: boolean, uri: URI | 'current' }) => {
const accessor = useAccessor()
const editCodeService = accessor.get('IEditCodeService')
const metricsService = accessor.get('IMetricsService')
const {
currStreamState,
isDisabled,
getStreamState,
} = useApplyButtonState({ applyBoxId, uri })
const onClickSubmit = useCallback(async () => {
if (isDisabled) return
if (getStreamState() === 'streaming') return
const opts = {
from: 'ClickApply',
applyStr: codeStr,
uri: uri,
startBehavior: 'reject-conflicts',
} as const
await editCodeService.callBeforeStartApplying(opts)
const [newApplyingUri, applyDonePromise] = editCodeService.startApplying(opts) ?? []
// catch any errors by interrupting the stream
applyDonePromise?.catch(e => { if (newApplyingUri) editCodeService.interruptURIStreaming({ uri: newApplyingUri }) })
applyingURIOfApplyBoxIdRef.current[applyBoxId] = newApplyingUri ?? undefined
// rerender(c => c + 1)
metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only
}, [isDisabled, getStreamState, editCodeService, codeStr, uri, applyBoxId, metricsService])
const onInterrupt = useCallback(() => {
if (getStreamState() !== 'streaming') return
const uri = getUriBeingApplied(applyBoxId)
if (!uri) return
editCodeService.interruptURIStreaming({ uri })
metricsService.capture('Stop Apply', {})
}, [getStreamState, applyBoxId, editCodeService, metricsService])
const onAccept = useCallback(() => {
const uri = getUriBeingApplied(applyBoxId)
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false })
}, [applyBoxId, editCodeService])
const onReject = useCallback(() => {
const uri = getUriBeingApplied(applyBoxId)
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
}, [applyBoxId, editCodeService])
// const onReapply = useCallback(() => {
// onReject()
// onClickSubmit()
// }, [onReject, onClickSubmit])
if (currStreamState === 'streaming') {
return <IconShell1 Icon={Square} onClick={onInterrupt} />
}
if (currStreamState === 'idle-no-changes') {
return <IconShell1 Icon={reapplyIcon ? RotateCw : Play} onClick={onClickSubmit} />
}
if (currStreamState === 'idle-has-changes') {
return <>
{/* <IconShell1
Icon={RotateCw}
onClick={onReapply}
/> */}
<IconShell1
Icon={X}
onClick={onReject}
className="text-red-600"
/>
<IconShell1
Icon={Check}
onClick={onAccept}
className="text-green-600"
/>
</>
}
}
export const BlockCodeApplyWrapper = ({
children,
initValue,
@ -315,10 +310,10 @@ export const BlockCodeApplyWrapper = ({
language: string;
uri: URI | 'current',
}) => {
const { statusIndicatorHTML, buttonsHTML } = useApplyButtonHTML({ codeStr: initValue, applyBoxId, uri })
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const { currStreamState } = useApplyButtonState({ applyBoxId, uri })
const name = uri !== 'current' ?
<ListableToolItem
@ -334,13 +329,15 @@ export const BlockCodeApplyWrapper = ({
{/* header */}
<div className=" select-none flex justify-between items-center py-1 px-2 border-b border-void-border-3 cursor-default">
<div className="flex items-center">
{statusIndicatorHTML}
<StatusIndicatorHTML uri={uri} applyBoxId={applyBoxId} />
<span className="text-[13px] font-light text-void-fg-3">
{name}
</span>
</div>
<div className={`${canApply ? '' : 'hidden'} flex items-center gap-1`}>
{buttonsHTML}
<JumpToFileButton uri={uri} />
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={initValue} />}
<ApplyButtonsHTML uri={uri} applyBoxId={applyBoxId} codeStr={initValue} reapplyIcon={false} />
</div>
</div>

View file

@ -63,11 +63,14 @@ export const QuickEditChat = ({
if (isStreamingRef.current) return
textAreaFnsRef.current?.disable()
const [newApplyingUri, applyDonePromise] = await editCodeService.startApplying({
const opts = {
from: 'QuickEdit',
diffareaid,
startBehavior: 'keep-conflicts',
}) ?? []
} as const
await editCodeService.callBeforeStartApplying(opts)
const [newApplyingUri, applyDonePromise] = editCodeService.startApplying(opts) ?? []
// catch any errors by interrupting the stream
applyDonePromise?.catch(e => { if (newApplyingUri) editCodeService.interruptCtrlKStreaming({ diffareaid }) })

View file

@ -72,7 +72,7 @@ export const SidebarThreadSelector = () => {
let firstMsg = null;
// let secondMsg = null;
const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role !== 'tool' && msg.role !== 'tool_request');
const firstUserMsgIdx = pastThread.messages.findIndex((msg) => msg.role === 'user');
if (firstUserMsgIdx !== -1) {
// firstMsg = truncate(pastThread.messages[firstMsgIdx].displayContent ?? '');

View file

@ -22,7 +22,6 @@ import { IThemeService } from '../../../../../../../platform/theme/common/themeS
import { ILLMMessageService } from '../../../../common/sendLLMMessageService.js';
import { IRefreshModelService } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js';
import { IVoidSettingsService } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js';
import { IEditCodeService } from '../../../editCodeServiceInterface.js'
import { ISidebarStateService } from '../../../sidebarStateService.js';
import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'
@ -46,6 +45,8 @@ import { ILanguageService } from '../../../../../../../editor/common/languages/l
import { IVoidModelService } from '../../../../common/voidModelService.js'
import { IWorkspaceContextService } from '../../../../../../../platform/workspace/common/workspace.js'
import { IVoidCommandBarService } from '../../../voidCommandBarService.js'
import { INativeHostService } from '../../../../../../../platform/native/common/native.js';
import { IEditCodeService } from '../../../editCodeServiceInterface.js'
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
@ -213,6 +214,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
IWorkspaceContextService: accessor.get(IWorkspaceContextService),
IVoidCommandBarService: accessor.get(IVoidCommandBarService),
INativeHostService: accessor.get(INativeHostService),
} as const
return reactAccessor

View file

@ -262,7 +262,6 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
const settingsState = useSettingsState()
const settingValue = settingsState.settingsOfProvider[providerName][settingName] as string // this should always be a string in this component
console.log(`providerName:${providerName} settingName: ${settingName}, settingValue: ${settingValue}`)
if (typeof settingValue !== 'string') {
console.log('Error: Provider setting had a non-string value.')
return
@ -682,6 +681,8 @@ const OneClickSwitchButton = () => {
const GeneralTab = () => {
const accessor = useAccessor()
const commandService = accessor.get('ICommandService')
const environmentService = accessor.get('IEnvironmentService')
const nativeHostService = accessor.get('INativeHostService')
return <>
@ -713,6 +714,11 @@ const GeneralTab = () => {
Theme Settings
</VoidButton>
</div>
<div className='my-4'>
<VoidButton onClick={() => { nativeHostService.showItemInFolder(environmentService.logsHome.fsPath) }}>
Open Logs
</VoidButton>
</div>
</div>

View file

@ -14,7 +14,6 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { IRange } from '../../../../editor/common/core/range.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { VOID_VIEW_ID } from './sidebarPane.js';
import { IMetricsService } from '../common/metricsService.js';
import { ISidebarStateService } from './sidebarStateService.js';
@ -53,23 +52,41 @@ export const roundRangeToLines = (range: IRange | null | undefined, options: { e
return newRange
}
const getContentInRange = (model: ITextModel, range: IRange | null) => {
if (!range)
return null
const content = model.getValueInRange(range)
const trimmedContent = content
.replace(/^\s*\n/g, '') // trim pure whitespace lines from start
.replace(/\n\s*$/g, '') // trim pure whitespace lines from end
return trimmedContent
}
// const getContentInRange = (model: ITextModel, range: IRange | null) => {
// if (!range)
// return null
// const content = model.getValueInRange(range)
// const trimmedContent = content
// .replace(/^\s*\n/g, '') // trim pure whitespace lines from start
// .replace(/\n\s*$/g, '') // trim pure whitespace lines from end
// return trimmedContent
// }
const findMatchingStagingIndex = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem) => {
return currentSelections?.findIndex(s =>
s.fileURI.fsPath === newSelection.fileURI.fsPath
&& s.range?.startLineNumber === newSelection.range?.startLineNumber
&& s.range?.endLineNumber === newSelection.range?.endLineNumber
)
const findStagingItemToReplace = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem): [number, StagingSelectionItem] | null => {
if (!currentSelections) return null
for (let i = 0; i < currentSelections.length; i += 1) {
const s = currentSelections[i]
if (s.uri.fsPath !== newSelection.uri.fsPath) continue
if (s.type === 'File' && newSelection.type === 'File') {
return [i, s] as const
}
if (s.type === 'CodeSelection' && newSelection.type === 'CodeSelection') {
if (s.uri.fsPath !== newSelection.uri.fsPath) continue
// if there's any collision return true
const [oldStart, oldEnd] = s.range
const [newStart, newEnd] = newSelection.range
if (oldStart !== newStart || oldEnd !== newEnd) continue
return [i, s] as const
}
if (s.type === 'Folder' && newSelection.type === 'Folder') {
return [i, s] as const
}
}
return null
}
const VOID_OPEN_SIDEBAR_ACTION_ID = 'void.sidebar.open'
@ -114,22 +131,18 @@ registerAction2(class extends Action2 {
editor?.setSelection({ startLineNumber: selectionRange.startLineNumber, endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER })
}
const selectionStr = getContentInRange(model, selectionRange)
const selection: StagingSelectionItem = !selectionRange || !selectionStr || (selectionRange.startLineNumber > selectionRange.endLineNumber) ? {
const selection: StagingSelectionItem = !selectionRange || (selectionRange.startLineNumber > selectionRange.endLineNumber) ? {
type: 'File',
fileURI: model.uri,
uri: model.uri,
language: model.getLanguageId(),
selectionStr: null,
range: null,
state: { isOpened: false, wasAddedAsCurrentFile: false }
state: { wasAddedAsCurrentFile: false }
} : {
type: 'Selection',
fileURI: model.uri,
type: 'CodeSelection',
uri: model.uri,
language: model.getLanguageId(),
selectionStr: selectionStr,
range: selectionRange,
state: { isOpened: true, wasAddedAsCurrentFile: false }
range: [selectionRange.startLineNumber, selectionRange.endLineNumber],
state: { wasAddedAsCurrentFile: false }
}
// update the staging selections
@ -149,17 +162,18 @@ registerAction2(class extends Action2 {
setSelections = (s) => chatThreadService.setCurrentMessageState(focusedMessageIdx, { stagingSelections: s })
}
// close all selections besides the new one
selections = selections.map(s => ({ ...s, state: { ...s.state, isOpened: false } }))
// if matches with existing selection, overwrite (since text may change)
const matchingStagingEltIdx = findMatchingStagingIndex(selections, selection)
if (matchingStagingEltIdx !== undefined && matchingStagingEltIdx !== -1) {
setSelections([
...selections!.slice(0, matchingStagingEltIdx),
selection,
...selections!.slice(matchingStagingEltIdx + 1, Infinity)
])
const replaceRes = findStagingItemToReplace(selections, selection)
if (replaceRes) {
const [idx, newSel] = replaceRes
if (idx !== undefined && idx !== -1) {
setSelections([
...selections!.slice(0, idx),
newSel,
...selections!.slice(idx + 1, Infinity)
])
}
}
// if no match, add it
else {
@ -200,7 +214,11 @@ registerAction2(class extends Action2 {
id: 'void.newChatAction',
title: 'New Chat',
icon: { id: 'add' },
menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VOID_VIEW_ID), }]
menu: [{ id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VOID_VIEW_ID), }],
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL,
weight: KeybindingWeight.VoidExtension,
},
});
}
async run(accessor: ServicesAccessor): Promise<void> {

View file

@ -8,11 +8,14 @@ import { QueryBuilder } from '../../../services/search/common/queryBuilder.js'
import { ISearchService } from '../../../services/search/common/search.js'
import { IEditCodeService } from './editCodeServiceInterface.js'
import { ITerminalToolService } from './terminalToolService.js'
import { ToolCallParams, ToolDirectoryItem, ToolName, ToolResultType } from '../common/toolsServiceTypes.js'
import { ToolCallParams, ToolName, ToolResultType } from '../common/toolsServiceTypes.js'
import { IVoidModelService } from '../common/voidModelService.js'
import { EndOfLinePreference } from '../../../../editor/common/model.js'
import { basename } from '../../../../base/common/path.js'
import { IVoidCommandBarService } from './voidCommandBarService.js'
import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree1Deep } from './directoryStrService.js'
import { IMarkerService } from '../../../../platform/markers/common/markers.js'
import { timeout } from '../../../../base/common/async.js'
// tool use for AI
@ -22,83 +25,20 @@ import { IVoidCommandBarService } from './voidCommandBarService.js'
type ValidateParams = { [T in ToolName]: (p: string) => Promise<ToolCallParams[T]> }
type CallTool = { [T in ToolName]: (p: ToolCallParams[T]) => Promise<{ result: ToolResultType[T], interruptTool?: () => void }> }
type ToolResultToString = { [T in ToolName]: (p: ToolCallParams[T], result: ToolResultType[T]) => string }
type ToolResultToString = { [T in ToolName]: (p: ToolCallParams[T], result: Awaited<ToolResultType[T]>) => string }
// pagination info
const MAX_FILE_CHARS_PAGE = 50_000
const MAX_CHILDREN_URIs_PAGE = 500
export const MAX_FILE_CHARS_PAGE = 50_000
export const MAX_CHILDREN_URIs_PAGE = 500
export const MAX_TERMINAL_CHARS_PAGE = 20_000
export const TERMINAL_TIMEOUT_TIME = 15
export const TERMINAL_BG_WAIT_TIME = 1
const computeDirectoryResult = async (
fileService: IFileService,
rootURI: URI,
pageNumber: number = 1
): Promise<ToolResultType['list_dir']> => {
const stat = await fileService.resolve(rootURI, { resolveMetadata: false });
if (!stat.isDirectory) {
return { children: null, hasNextPage: false, hasPrevPage: false, itemsRemaining: 0 };
}
const originalChildrenLength = stat.children?.length ?? 0;
const fromChildIdx = MAX_CHILDREN_URIs_PAGE * (pageNumber - 1);
const toChildIdx = MAX_CHILDREN_URIs_PAGE * pageNumber - 1; // INCLUSIVE
const listChildren = stat.children?.slice(fromChildIdx, toChildIdx + 1) ?? [];
const children: ToolDirectoryItem[] = listChildren.map(child => ({
name: child.name,
uri: child.resource,
isDirectory: child.isDirectory,
isSymbolicLink: child.isSymbolicLink
}));
const hasNextPage = (originalChildrenLength - 1) > toChildIdx;
const hasPrevPage = pageNumber > 1;
const itemsRemaining = Math.max(0, originalChildrenLength - (toChildIdx + 1));
return {
children,
hasNextPage,
hasPrevPage,
itemsRemaining
};
};
const directoryResultToString = (params: ToolCallParams['list_dir'], result: ToolResultType['list_dir']): string => {
if (!result.children) {
return `Error: ${params.rootURI} is not a directory`;
}
let output = '';
const entries = result.children;
if (!result.hasPrevPage) { // is first page
output += `${params.rootURI.fsPath}\n`;
}
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
const isLast = i === entries.length - 1 && !result.hasNextPage;
const prefix = isLast ? '└── ' : '├── ';
output += `${prefix}${entry.name}${entry.isDirectory ? '/' : ''}${entry.isSymbolicLink ? ' (symbolic link)' : ''}\n`;
}
if (result.hasNextPage) {
output += `└── (${result.itemsRemaining} results remaining...)\n`;
}
return output;
};
const validateJSON = (s: string): { [s: string]: unknown } => {
@ -117,7 +57,9 @@ const validateJSON = (s: string): { [s: string]: unknown } => {
}
}
const isFalsy = (u: unknown) => {
return !u || u === 'null' || u === 'undefined'
}
const validateStr = (argName: string, value: unknown) => {
if (typeof value !== 'string') throw new Error(`Invalid LLM output format: ${argName} must be a string.`)
@ -126,13 +68,24 @@ const validateStr = (argName: string, value: unknown) => {
// We are NOT checking to make sure in workspace
// TODO!!!! check to make sure folder/file exists
const validateURI = (uriStr: unknown) => {
if (typeof uriStr !== 'string') throw new Error('Invalid LLM output format: Provided uri must be a string.')
const uri = URI.file(uriStr)
return uri
}
const validateOptionalURI = (uriStr: unknown) => {
if (isFalsy(uriStr)) return null
return validateURI(uriStr)
}
const validateOptionalStr = (argName: string, str: unknown) => {
if (isFalsy(str)) return null
return validateStr(argName, str)
}
const validatePageNum = (pageNumberUnknown: unknown) => {
if (!pageNumberUnknown) return 1
const parsedInt = Number.parseInt(pageNumberUnknown + '')
@ -141,6 +94,20 @@ const validatePageNum = (pageNumberUnknown: unknown) => {
return parsedInt
}
const validateNumber = (numStr: unknown, opts: { default: number | null }) => {
if (typeof numStr === 'number')
return numStr
if (isFalsy(numStr)) return opts.default
if (typeof numStr === 'string') {
const parsedInt = Number.parseInt(numStr + '')
if (!Number.isInteger(parsedInt)) return opts.default
return parsedInt
}
return opts.default
}
const validateRecursiveParamStr = (paramsUnknown: unknown) => {
if (typeof paramsUnknown !== 'string') throw new Error('Invalid LLM output format: Error calling tool: provided params must be a string.')
const params = paramsUnknown
@ -154,12 +121,15 @@ const validateProposedTerminalId = (terminalIdUnknown: unknown) => {
return terminalId
}
const validateWaitForCompletion = (b: unknown) => {
const validateBoolean = (b: unknown, opts: { default: boolean }) => {
if (typeof b === 'string') {
if (b === 'true') return true
if (b === 'false') return false
}
return true // default is true
if (typeof b === 'boolean') {
return b
}
return opts.default
}
@ -195,6 +165,8 @@ export class ToolsService implements IToolsService {
@IEditCodeService editCodeService: IEditCodeService,
@ITerminalToolService private readonly terminalToolService: ITerminalToolService,
@IVoidCommandBarService private readonly commandBarService: IVoidCommandBarService,
@IDirectoryStrService private readonly directoryStrService: IDirectoryStrService,
@IMarkerService private readonly markerService: IMarkerService,
) {
const queryBuilder = instantiationService.createInstance(QueryBuilder);
@ -202,14 +174,17 @@ export class ToolsService implements IToolsService {
this.validateParams = {
read_file: async (params: string) => {
const o = validateJSON(params)
const { uri: uriStr, pageNumber: pageNumberUnknown } = o
const { uri: uriStr, startLine: startLineUnknown, endLine: endLineUnknown, pageNumber: pageNumberUnknown } = o
const uri = validateURI(uriStr)
const pageNumber = validatePageNum(pageNumberUnknown)
return { uri, pageNumber }
const startLine = validateNumber(startLineUnknown, { default: null })
const endLine = validateNumber(endLineUnknown, { default: null })
return { uri, startLine, endLine, pageNumber }
},
list_dir: async (params: string) => {
ls_dir: async (params: string) => {
const o = validateJSON(params)
const { uri: uriStr, pageNumber: pageNumberUnknown } = o
@ -217,29 +192,48 @@ export class ToolsService implements IToolsService {
const pageNumber = validatePageNum(pageNumberUnknown)
return { rootURI: uri, pageNumber }
},
pathname_search: async (params: string) => {
get_dir_structure: async (params: string) => {
const o = validateJSON(params)
const { query: queryUnknown, pageNumber: pageNumberUnknown } = o
const { uri: uriStr, } = o
const uri = validateURI(uriStr)
return { rootURI: uri }
},
search_pathnames_only: async (params: string) => {
const o = validateJSON(params)
const {
query: queryUnknown,
include: includeUnknown,
pageNumber: pageNumberUnknown
} = o
const queryStr = validateStr('query', queryUnknown)
const pageNumber = validatePageNum(pageNumberUnknown)
const include = validateOptionalStr('include', includeUnknown)
return { queryStr, pageNumber }
return { queryStr, include, pageNumber }
},
grep_search: async (params: string) => {
search_files: async (params: string) => {
const o = validateJSON(params)
const { query: queryUnknown, pageNumber: pageNumberUnknown } = o
const {
query: queryUnknown,
searchInFolder: searchInFolderUnknown,
isRegex: isRegexUnknown,
pageNumber: pageNumberUnknown
} = o
const queryStr = validateStr('query', queryUnknown)
const pageNumber = validatePageNum(pageNumberUnknown)
return { queryStr, pageNumber }
const searchInFolder = validateOptionalURI(searchInFolderUnknown)
const isRegex = validateBoolean(isRegexUnknown, { default: false })
return { queryStr, searchInFolder, isRegex, pageNumber }
},
// ---
create_uri: async (params: string) => {
create_file_or_folder: async (params: string) => {
const o = validateJSON(params)
const { uri: uriUnknown } = o
const uri = validateURI(uriUnknown)
@ -248,7 +242,7 @@ export class ToolsService implements IToolsService {
return { uri, isFolder }
},
delete_uri: async (params: string) => {
delete_file_or_folder: async (params: string) => {
const o = validateJSON(params)
const { uri: uriUnknown, params: paramsStr } = o
const uri = validateURI(uriUnknown)
@ -258,7 +252,7 @@ export class ToolsService implements IToolsService {
return { uri, isRecursive, isFolder }
},
edit: async (params: string) => {
edit_file: async (params: string) => {
const o = validateJSON(params)
const { uri: uriStr, changeDescription: changeDescriptionUnknown } = o
const uri = validateURI(uriStr)
@ -266,12 +260,12 @@ export class ToolsService implements IToolsService {
return { uri, changeDescription }
},
terminal_command: async (s: string) => {
run_terminal_command: async (s: string) => {
const o = validateJSON(s)
const { command: commandUnknown, terminalId: terminalIdUnknown, waitForCompletion: waitForCompletionUnknown } = o
const command = validateStr('command', commandUnknown)
const proposedTerminalId = validateProposedTerminalId(terminalIdUnknown)
const waitForCompletion = validateWaitForCompletion(waitForCompletionUnknown)
const waitForCompletion = validateBoolean(waitForCompletionUnknown, { default: true })
return { command, proposedTerminalId, waitForCompletion }
},
@ -279,28 +273,46 @@ export class ToolsService implements IToolsService {
this.callTool = {
read_file: async ({ uri, pageNumber }) => {
read_file: async ({ uri, startLine, endLine, pageNumber }) => {
await voidModelService.initializeModel(uri)
const { model } = await voidModelService.getModelSafe(uri)
if (model === null) { throw new Error(`Contents were empty. There may have been an error, or the file may not exist.`) }
const readFileContents = model.getValue(EndOfLinePreference.LF)
let contents: string
if (startLine === null && endLine === null) {
contents = model.getValue(EndOfLinePreference.LF)
}
else {
const startLineNumber = startLine === null ? 1 : startLine
const endLineNumber = endLine === null ? model.getLineCount() : endLine
contents = model.getValueInRange({ startLineNumber, startColumn: 1, endLineNumber, endColumn: Number.MAX_SAFE_INTEGER }, EndOfLinePreference.LF)
}
const fromIdx = MAX_FILE_CHARS_PAGE * (pageNumber - 1)
const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1
const fileContents = readFileContents.slice(fromIdx, toIdx + 1) // paginate
const hasNextPage = (readFileContents.length - 1) - toIdx >= 1
const fileContents = contents.slice(fromIdx, toIdx + 1) // paginate
const hasNextPage = (contents.length - 1) - toIdx >= 1
return { result: { fileContents, hasNextPage } }
},
list_dir: async ({ rootURI, pageNumber }) => {
const dirResult = await computeDirectoryResult(fileService, rootURI, pageNumber)
ls_dir: async ({ rootURI, pageNumber }) => {
const dirResult = await computeDirectoryTree1Deep(fileService, rootURI, pageNumber)
return { result: dirResult }
},
pathname_search: async ({ queryStr, pageNumber }) => {
get_dir_structure: async ({ rootURI }) => {
const result = await this.directoryStrService.getDirectoryStrTool(rootURI)
let str = result.str
if (result.wasCutOff) str += '\n(Result was truncated)'
return { result: { str } }
},
search_pathnames_only: async ({ queryStr, include, pageNumber }) => {
const query = queryBuilder.file(workspaceContextService.getWorkspace().folders.map(f => f.uri), {
filePattern: queryStr,
includePattern: include ?? undefined,
})
const data = await searchService.fileSearch(query, CancellationToken.None)
@ -314,11 +326,15 @@ export class ToolsService implements IToolsService {
return { result: { uris, hasNextPage } }
},
grep_search: async ({ queryStr, pageNumber }) => {
search_files: async ({ queryStr, isRegex, searchInFolder, pageNumber }) => {
const searchFolders = searchInFolder === null ?
workspaceContextService.getWorkspace().folders.map(f => f.uri)
: [searchInFolder]
const query = queryBuilder.text({
pattern: queryStr,
isRegExp: true,
}, workspaceContextService.getWorkspace().folders.map(f => f.uri))
isRegExp: isRegex,
}, searchFolders)
const data = await searchService.textSearch(query, CancellationToken.None)
@ -334,7 +350,7 @@ export class ToolsService implements IToolsService {
// ---
create_uri: async ({ uri, isFolder }) => {
create_file_or_folder: async ({ uri, isFolder }) => {
if (isFolder)
await fileService.createFolder(uri)
else {
@ -343,31 +359,46 @@ export class ToolsService implements IToolsService {
return { result: {} }
},
delete_uri: async ({ uri, isRecursive }) => {
delete_file_or_folder: async ({ uri, isRecursive }) => {
await fileService.del(uri, { recursive: isRecursive })
return { result: {} }
},
edit: async ({ uri, changeDescription }) => {
edit_file: async ({ uri, changeDescription }) => {
await voidModelService.initializeModel(uri)
if (this.commandBarService.getStreamState(uri) === 'streaming') {
throw new Error(`The Apply model was already running. This can happen if two agents try editing the same file at the same time. Please try again in a moment.`)
throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and resume later.`)
}
const res = await editCodeService.startApplying({
const opts = {
uri,
applyStr: changeDescription,
from: 'ClickApply',
startBehavior: 'keep-conflicts',
})
} as const
await editCodeService.callBeforeStartApplying(opts)
const res = editCodeService.startApplying(opts)
if (!res) throw new Error(`The Apply model did not start running on ${basename(uri.fsPath)}. Please try again.`)
const [diffZoneURI, applyDonePromise] = res
const interruptTool = () => { // must reject the applyPromiseDone promise
editCodeService.interruptURIStreaming({ uri: diffZoneURI })
}
return { result: applyDonePromise, interruptTool }
const lintErrorsPromise = applyDonePromise.then(async () => {
await timeout(500)
const lintErrorsStr = this.markerService
.read({ resource: uri })
.map(l => l.message)
.join('\n')
if (!lintErrorsStr) return { lintErrorsStr: null }
return { lintErrorsStr }
})
return { result: lintErrorsPromise, interruptTool }
},
terminal_command: async ({ command, proposedTerminalId, waitForCompletion }) => {
run_terminal_command: async ({ command, proposedTerminalId, waitForCompletion }) => {
const { terminalId, didCreateTerminal, result, resolveReason } = await this.terminalToolService.runCommand(command, proposedTerminalId, waitForCompletion)
return { result: { terminalId, didCreateTerminal, result, resolveReason } }
},
@ -381,29 +412,31 @@ export class ToolsService implements IToolsService {
read_file: (params, result) => {
return result.fileContents + nextPageStr(result.hasNextPage)
},
list_dir: (params, result) => {
const dirTreeStr = directoryResultToString(params, result)
ls_dir: (params, result) => {
const dirTreeStr = stringifyDirectoryTree1Deep(params, result)
return dirTreeStr // + nextPageStr(result.hasNextPage) // already handles num results remaining
},
pathname_search: (params, result) => {
get_dir_structure: (params, result) => {
return result.str
},
search_pathnames_only: (params, result) => {
return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage)
},
grep_search: (params, result) => {
search_files: (params, result) => {
return result.uris.map(uri => uri.fsPath).join('\n') + nextPageStr(result.hasNextPage)
},
// ---
create_uri: (params, result) => {
create_file_or_folder: (params, result) => {
return `URI ${params.uri.fsPath} successfully created.`
},
delete_uri: (params, result) => {
delete_file_or_folder: (params, result) => {
return `URI ${params.uri.fsPath} successfully deleted.`
},
edit: (params, result) => {
console.log('STR OF RESULT', params)
return `Change successfully made to ${params.uri.fsPath}.`
edit_file: (params, result) => {
const additionalStr = result.lintErrorsStr ? `Lint errors found after change:\n${result.lintErrorsStr}.\nIf this is related to a change made while calling this tool, you might want to fix the error.` : `No lint errors found.`
return `Change successfully made to ${params.uri.fsPath}. ${additionalStr}`
},
terminal_command: (params, result) => {
run_terminal_command: (params, result) => {
const {
terminalId,
didCreateTerminal,

View file

@ -173,7 +173,7 @@ export class VoidCommandBarService extends Disposable implements IVoidCommandBar
}
}))
this._register(this._editCodeService.onDidChangeDiffsInDiffZone(e => {
this._register(this._editCodeService.onDidChangeDiffsInDiffZoneNotStreaming(e => {
for (const uri of this._listenToTheseURIs) {
if (e.uri.fsPath !== uri.fsPath) continue
// --- sortedURIs: no change

View file

@ -1,29 +1,57 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { URI } from '../../../../base/common/uri.js';
import { IRange } from '../../../../editor/common/core/range.js';
import { VoidFileSnapshot } from './editCodeServiceTypes.js';
import { AnthropicReasoning } from './sendLLMMessageTypes.js';
import { ToolName, ToolCallParams, ToolResultType } from './toolsServiceTypes.js';
export type ToolMessage<T extends ToolName> = {
role: 'tool';
name: T; // internal use
paramsStr: string; // internal use
id: string; // apis require this tool use id
content: string; // give this result to LLM
content: string; // give this result to LLM (string of value)
} & (
// in order of events:
| { type: 'invalid_params', result: null, params: null, name: string }
// if rejected, don't show in chat
result:
| { type: 'success'; params: ToolCallParams[T]; value: ToolResultType[T], }
| { type: 'error'; params: ToolCallParams[T] | undefined; value: string }
| { type: 'rejected'; params: ToolCallParams[T] }
| { type: 'tool_request', result: null, name: T, params: ToolCallParams[T], } // params were validated, awaiting user
| { type: 'running_now', result: null, name: T, params: ToolCallParams[T], }
| { type: 'tool_error', result: string, name: T, params: ToolCallParams[T], } // error when tool was running
| { type: 'success', result: ToolResultType[T], name: T, params: ToolCallParams[T], }
| { type: 'rejected', result: null, name: T, params: ToolCallParams[T], }
) // user rejected
export type DecorativeCanceledTool = {
role: 'decorative_canceled_tool';
name: string;
}
export type ToolRequestApproval<T extends ToolName> = {
role: 'tool_request';
name: T; // internal use
params: ToolCallParams[T]; // internal use
paramsStr: string; // internal use - this is what the LLM outputted, not necessarily JSON.stringify(params)
id: string; // proposed tool's id
// export type ToolRequestApproval<T extends ToolName> = {
// role: 'tool_request';
// name: T; // internal use
// params: ToolCallParams[T]; // internal use
// paramsStr: string; // internal use - this is what the LLM outputted, not necessarily JSON.stringify(params)
// id: string; // proposed tool's id
// }
// checkpoints
export type CheckpointEntry = {
role: 'checkpoint';
type: 'user_edit' | 'tool_edit';
voidFileSnapshotOfURI: { [fsPath: string]: VoidFileSnapshot | undefined };
userModifications: {
voidFileSnapshotOfURI: { [fsPath: string]: VoidFileSnapshot | undefined };
};
}
// WARNING: changing this format is a big deal!!!!!! need to migrate old format to new format on users' computers so people don't get errors.
export type ChatMessage =
| {
@ -43,38 +71,31 @@ export type ChatMessage =
anthropicReasoning: AnthropicReasoning[] | null; // anthropic reasoning
}
| ToolMessage<ToolName>
| ToolRequestApproval<ToolName>
| DecorativeCanceledTool
| CheckpointEntry
// one of the square items that indicates a selection in a chat bubble (NOT a file, a Selection of text)
export type CodeSelection = {
type: 'Selection';
fileURI: URI;
language: string;
selectionStr: string;
range: IRange;
state: {
isOpened: boolean;
wasAddedAsCurrentFile: boolean;
};
}
export type FileSelection = {
// one of the square items that indicates a selection in a chat bubble
export type StagingSelectionItem = {
type: 'File';
fileURI: URI;
uri: URI;
language: string;
selectionStr: null;
range: null;
state: {
isOpened: boolean;
wasAddedAsCurrentFile: boolean;
};
state: { wasAddedAsCurrentFile: boolean; };
} | {
type: 'CodeSelection';
range: [number, number];
uri: URI;
language: string;
state: { wasAddedAsCurrentFile: boolean; };
} | {
type: 'Folder';
uri: URI;
language?: undefined;
state?: undefined;
}
export type StagingSelectionItem = CodeSelection | FileSelection
// a link to a symbol (an underlined link to a piece of code)
export type CodespanLocationLink = {
uri: URI, // we handle serialization for this
displayText: string,

View file

@ -0,0 +1,10 @@
import { URI } from '../../../../base/common/uri.js';
export type VoidDirectoryItem = {
uri: URI;
name: string;
isSymbolicLink: boolean;
children: VoidDirectoryItem[] | null;
isDirectory: boolean;
isGitIgnoredDirectory: false | { numChildren: number }; // if directory is gitignored, we ignore children
}

View file

@ -0,0 +1,119 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { URI } from '../../../../base/common/uri.js';
export type ComputedDiff = {
type: 'edit';
originalCode: string;
originalStartLine: number;
originalEndLine: number;
code: string;
startLine: number; // 1-indexed
endLine: number;
} | {
type: 'insertion';
// originalCode: string;
originalStartLine: number; // insertion starts on column 0 of this
// originalEndLine: number;
code: string;
startLine: number;
endLine: number;
} | {
type: 'deletion';
originalCode: string;
originalStartLine: number;
originalEndLine: number;
// code: string;
startLine: number; // deletion starts on column 0 of this
// endLine: number;
}
// ---------- Diff types ----------
export type CommonZoneProps = {
diffareaid: number;
startLine: number;
endLine: number;
_URI: URI; // typically we get the URI from model
}
export type CtrlKZone = {
type: 'CtrlKZone';
originalCode?: undefined;
editorId: string; // the editor the input lives on
// _ means anything we don't include if we clone it
_mountInfo: null | {
textAreaRef: { current: HTMLTextAreaElement | null }
dispose: () => void;
refresh: () => void;
}
_linkedStreamingDiffZone: number | null; // diffareaid of the diffZone currently streaming here
_removeStylesFns: Set<Function> // these don't remove diffs or this diffArea, only their styles
} & CommonZoneProps
export type TrackingZone<T> = {
type: 'TrackingZone';
metadata: T;
originalCode?: undefined;
editorId?: undefined;
_removeStylesFns?: undefined;
} & CommonZoneProps
// called DiffArea for historical purposes, we can rename to something like TextRegion if we want
export type DiffArea = CtrlKZone | DiffZone | TrackingZone<any>
export type Diff = {
diffid: number;
diffareaid: number; // the diff area this diff belongs to, "computed"
} & ComputedDiff
export type DiffZone = {
type: 'DiffZone',
originalCode: string;
_diffOfId: Record<string, Diff>; // diffid -> diff in this DiffArea
_streamState: {
isStreaming: true;
streamRequestIdRef: { current: string | null };
line: number;
} | {
isStreaming: false;
streamRequestIdRef?: undefined;
line?: undefined;
};
editorId?: undefined;
linkedStreamingDiffZone?: undefined;
_removeStylesFns: Set<Function> // these don't remove diffs or this diffArea, only their styles
} & CommonZoneProps
export const diffAreaSnapshotKeys = [
'type',
'diffareaid',
'originalCode',
'startLine',
'endLine',
'editorId',
] as const satisfies (keyof DiffArea)[]
export type DiffAreaSnapshotEntry<DiffAreaType extends DiffArea = DiffArea> = Pick<DiffAreaType, typeof diffAreaSnapshotKeys[number]>
export type VoidFileSnapshot = {
snapshottedDiffAreaOfId: Record<string, DiffAreaSnapshotEntry>;
entireFileCode: string;
}

View file

@ -3,7 +3,7 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { ModelSelectionOptions, ProviderName } from './voidSettingsTypes.js';
import { FeatureName, ModelSelectionOptions, ProviderName } from './voidSettingsTypes.js';
export const defaultModelsOfProvider = {
@ -25,11 +25,9 @@ export const defaultModelsOfProvider = {
'grok-3-latest',
],
gemini: [ // https://ai.google.dev/gemini-api/docs/models/gemini
'gemini-2.5-pro-exp-03-25',
'gemini-2.0-flash',
'gemini-1.5-flash',
'gemini-1.5-pro',
'gemini-1.5-flash-8b',
'gemini-2.0-flash-thinking-exp',
'gemini-2.0-flash-lite',
],
deepseek: [ // https://api-docs.deepseek.com/quick_start/pricing
'deepseek-chat',
@ -44,8 +42,13 @@ export const defaultModelsOfProvider = {
'anthropic/claude-3.7-sonnet',
'anthropic/claude-3.5-sonnet',
'deepseek/deepseek-r1',
'deepseek/deepseek-r1-zero:free',
'mistralai/codestral-2501',
'qwen/qwen-2.5-coder-32b-instruct',
// 'mistralai/mistral-small-3.1-24b-instruct:free',
'google/gemini-2.0-flash-lite-preview-02-05:free',
// 'google/gemini-2.0-pro-exp-02-05:free',
// 'google/gemini-2.0-flash-exp:free',
],
groq: [ // https://console.groq.com/docs/models
'qwen-qwq-32b',
@ -69,8 +72,8 @@ export const defaultModelsOfProvider = {
type ModelOptions = {
contextWindow: number; // input tokens // <-- UNUSED
maxOutputTokens: number | null; // output tokens // <-- UNUSED
contextWindow: number; // input tokens
maxOutputTokens: number | null; // output tokens, defaults to 4092
cost: { // <-- UNUSED
input: number;
output: number;
@ -115,9 +118,9 @@ type ProviderSettings = {
const modelOptionsDefaults: ModelOptions = {
contextWindow: 32_000, // unused
maxOutputTokens: null, // unused
cost: { input: 0, output: 0 }, // unused
contextWindow: 32_000,
maxOutputTokens: 4_096,
cost: { input: 0, output: 0 },
supportsSystemMessage: false,
supportsTools: false,
supportsFIM: false,
@ -125,49 +128,106 @@ const modelOptionsDefaults: ModelOptions = {
}
// TODO!!! double check all context sizes below
// TODO!!! add openrouter common models
// TODO!!! allow user to modify capabilities and tell them if autodetected model or falling back
const openSourceModelOptions_assumingOAICompat = {
'deepseekR1': {
supportsFIM: false,
supportsSystemMessage: false,
supportsTools: false,
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: true, openSourceThinkTags: ['<think>', '</think>'] },
contextWindow: 32_000, maxOutputTokens: 4_096,
},
'deepseekCoderV3': {
supportsFIM: false,
supportsSystemMessage: false, // unstable
supportsTools: false,
reasoningCapabilities: false,
contextWindow: 32_000, maxOutputTokens: 4_096,
},
'deepseekCoderV2': {
supportsFIM: false,
supportsSystemMessage: false, // unstable
supportsTools: false,
reasoningCapabilities: false,
contextWindow: 32_000, maxOutputTokens: 4_096,
},
'codestral': {
supportsFIM: true,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
contextWindow: 32_000, maxOutputTokens: 4_096,
},
// llama
'openhands-lm-32b': { // https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false, // built on qwen 2.5 32B instruct
contextWindow: 128_000, maxOutputTokens: 4_096
},
'phi4': {
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: false,
reasoningCapabilities: false,
contextWindow: 16_000, maxOutputTokens: 4_096,
},
'gemma': { // https://news.ycombinator.com/item?id=43451406
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: false,
reasoningCapabilities: false,
contextWindow: 32_000, maxOutputTokens: 4_096,
},
// llama 4 https://ai.meta.com/blog/llama-4-multimodal-intelligence/
'llama4-scout': {
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
contextWindow: 10_000_000, maxOutputTokens: 4_096,
},
'llama4-maverick': {
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
contextWindow: 10_000_000, maxOutputTokens: 4_096,
},
// llama 3
'llama3': {
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
contextWindow: 32_000, maxOutputTokens: 4_096,
},
'llama3.1': {
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
contextWindow: 32_000, maxOutputTokens: 4_096,
},
'llama3.2': {
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
contextWindow: 32_000, maxOutputTokens: 4_096,
},
'llama3.3': {
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
contextWindow: 32_000, maxOutputTokens: 4_096,
},
// qwen
'qwen2.5coder': {
@ -175,12 +235,14 @@ const openSourceModelOptions_assumingOAICompat = {
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: false,
contextWindow: 32_000, maxOutputTokens: 4_096,
},
'qwq': {
supportsFIM: false, // no FIM, yes reasoning
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style',
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: true, openSourceThinkTags: ['<think>', '</think>'] },
contextWindow: 128_000, maxOutputTokens: 8_192,
},
// FIM only
'starcoder2': {
@ -188,19 +250,26 @@ const openSourceModelOptions_assumingOAICompat = {
supportsSystemMessage: false,
supportsTools: false,
reasoningCapabilities: false,
contextWindow: 128_000, maxOutputTokens: 8_192,
},
'codegemma:2b': {
supportsFIM: true,
supportsSystemMessage: false,
supportsTools: false,
reasoningCapabilities: false,
contextWindow: 128_000, maxOutputTokens: 8_192,
},
} as const satisfies { [s: string]: Partial<ModelOptions> }
} as const satisfies { [s: string]: Omit<ModelOptions, 'cost'> }
const extensiveModelFallback: ProviderSettings['modelOptionsFallback'] = (modelName) => {
const lower = modelName.toLowerCase()
const toFallback = (opts: Omit<ModelOptions, 'cost'>): ModelOptions & { modelName: string } => {
return {
modelName,
@ -209,16 +278,46 @@ const extensiveModelFallback: ProviderSettings['modelOptionsFallback'] = (modelN
cost: { input: 0, output: 0 },
}
}
if (modelName.includes('gpt-4o')) return toFallback(openAIModelOptions['gpt-4o'])
if (modelName.includes('claude-3-5') || modelName.includes('claude-3.5')) return toFallback(anthropicModelOptions['claude-3-5-sonnet-20241022'])
if (modelName.includes('claude')) return toFallback(anthropicModelOptions['claude-3-7-sonnet-20250219'])
if (modelName.includes('grok')) return toFallback(xAIModelOptions['grok-2'])
if (modelName.includes('deepseek-r1') || modelName.includes('deepseek-reasoner')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.deepseekR1, contextWindow: 32_000, maxOutputTokens: 4_096, })
if (modelName.includes('deepseek')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.deepseekCoderV2, contextWindow: 32_000, maxOutputTokens: 4_096, })
if (modelName.includes('llama3')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.llama3, contextWindow: 32_000, maxOutputTokens: 4_096, })
if (modelName.includes('qwen') && modelName.includes('2.5') && modelName.includes('coder')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['qwen2.5coder'], contextWindow: 32_000, maxOutputTokens: 4_096, })
if (modelName.includes('codestral')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.codestral, contextWindow: 32_000, maxOutputTokens: 4_096, })
if (/\bo1\b/.test(modelName) || /\bo3\b/.test(modelName)) return toFallback(openAIModelOptions['o1'])
if (Object.keys(openSourceModelOptions_assumingOAICompat).map(k => k.toLowerCase()).includes(lower))
return toFallback(openSourceModelOptions_assumingOAICompat[lower as keyof typeof openSourceModelOptions_assumingOAICompat])
if (lower.includes('gemini') && (lower.includes('2.5') || lower.includes('2-5'))) return toFallback(geminiModelOptions['gemini-2.5-pro-exp-03-25'])
if (lower.includes('claude-3-5') || lower.includes('claude-3.5')) return toFallback(anthropicModelOptions['claude-3-5-sonnet-20241022'])
if (lower.includes('claude')) return toFallback(anthropicModelOptions['claude-3-7-sonnet-20250219'])
if (lower.includes('grok')) return toFallback(xAIModelOptions['grok-2'])
if (lower.includes('deepseek-r1') || lower.includes('deepseek-reasoner')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.deepseekR1 })
if (lower.includes('deepseek') && lower.includes('v2')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.deepseekCoderV2 })
if (lower.includes('deepseek')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.deepseekCoderV3 })
if (lower.includes('llama3')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.llama3, })
if (lower.includes('llama3.1')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama3.1'], })
if (lower.includes('llama3.2')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama3.2'], })
if (lower.includes('llama3.3')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama3.3'], })
if (lower.includes('llama') || lower.includes('scout')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama4-scout'] })
if (lower.includes('llama') || lower.includes('maverick')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama4-scout'] })
if (lower.includes('llama')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['llama4-scout'] })
if (lower.includes('qwen') && lower.includes('2.5') && lower.includes('coder')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['qwen2.5coder'] })
if (lower.includes('qwq')) { return toFallback({ ...openSourceModelOptions_assumingOAICompat.qwq, }) }
if (lower.includes('phi4')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.phi4, })
if (lower.includes('codestral')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.codestral })
if (lower.includes('gemma')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.gemma, })
if (lower.includes('starcoder2')) return toFallback({ ...openSourceModelOptions_assumingOAICompat.starcoder2, })
if (lower.includes('openhands')) return toFallback({ ...openSourceModelOptions_assumingOAICompat['openhands-lm-32b'], }) // max output unclear
if (lower.includes('4o') && lower.includes('mini')) return toFallback(openAIModelOptions['gpt-4o-mini'])
if (lower.includes('4o')) return toFallback(openAIModelOptions['gpt-4o'])
if (lower.includes('o1') && lower.includes('mini')) return toFallback(openAIModelOptions['o1-mini'])
if (lower.includes('o1')) return toFallback(openAIModelOptions['o1'])
if (lower.includes('o3') && lower.includes('mini')) return toFallback(openAIModelOptions['o3-mini'])
// if (lower.includes('o3')) return toFallback(openAIModelOptions['o3'])
return toFallback(modelOptionsDefaults)
}
@ -294,12 +393,13 @@ const anthropicSettings: ProviderSettings = {
},
modelOptions: anthropicModelOptions,
modelOptionsFallback: (modelName) => {
const lower = modelName.toLowerCase()
let fallbackName: keyof typeof anthropicModelOptions | null = null
if (modelName.includes('claude-3-7-sonnet')) fallbackName = 'claude-3-7-sonnet-20250219'
if (modelName.includes('claude-3-5-sonnet')) fallbackName = 'claude-3-5-sonnet-20241022'
if (modelName.includes('claude-3-5-haiku')) fallbackName = 'claude-3-5-haiku-20241022'
if (modelName.includes('claude-3-opus')) fallbackName = 'claude-3-opus-20240229'
if (modelName.includes('claude-3-sonnet')) fallbackName = 'claude-3-sonnet-20240229'
if (lower.includes('claude-3-7-sonnet')) fallbackName = 'claude-3-7-sonnet-20250219'
if (lower.includes('claude-3-5-sonnet')) fallbackName = 'claude-3-5-sonnet-20241022'
if (lower.includes('claude-3-5-haiku')) fallbackName = 'claude-3-5-haiku-20241022'
if (lower.includes('claude-3-opus')) fallbackName = 'claude-3-opus-20240229'
if (lower.includes('claude-3-sonnet')) fallbackName = 'claude-3-sonnet-20240229'
if (fallbackName) return { modelName: fallbackName, ...anthropicModelOptions[fallbackName] }
return { modelName, ...modelOptionsDefaults, maxOutputTokens: 4_096 }
},
@ -359,10 +459,11 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
const openAISettings: ProviderSettings = {
modelOptions: openAIModelOptions,
modelOptionsFallback: (modelName) => {
const lower = modelName.toLowerCase()
let fallbackName: keyof typeof openAIModelOptions | null = null
if (modelName.includes('o1')) { fallbackName = 'o1' }
if (modelName.includes('o3-mini')) { fallbackName = 'o3-mini' }
if (modelName.includes('gpt-4o')) { fallbackName = 'gpt-4o' }
if (lower.includes('o1')) { fallbackName = 'o1' }
if (lower.includes('o3-mini')) { fallbackName = 'o3-mini' }
if (lower.includes('gpt-4o')) { fallbackName = 'gpt-4o' }
if (fallbackName) return { modelName: fallbackName, ...openAIModelOptions[fallbackName] }
return null
}
@ -384,8 +485,9 @@ const xAIModelOptions = {
const xAISettings: ProviderSettings = {
modelOptions: xAIModelOptions,
modelOptionsFallback: (modelName) => {
const lower = modelName.toLowerCase()
let fallbackName: keyof typeof xAIModelOptions | null = null
if (modelName.includes('grok-2')) fallbackName = 'grok-2'
if (lower.includes('grok-2')) fallbackName = 'grok-2'
if (fallbackName) return { modelName: fallbackName, ...xAIModelOptions[fallbackName] }
return null
}
@ -394,9 +496,18 @@ const xAISettings: ProviderSettings = {
// ---------------- GEMINI ----------------
const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
'gemini-2.5-pro-exp-03-25': {
contextWindow: 1_048_576,
maxOutputTokens: 8_192,
cost: { input: 0, output: 0 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
supportsTools: 'openai-style', // we are assuming OpenAI SDK when calling gemini
reasoningCapabilities: false,
},
'gemini-2.0-flash': {
contextWindow: 1_048_576,
maxOutputTokens: null, // 8_192,
maxOutputTokens: 8_192, // 8_192,
cost: { input: 0.10, output: 0.40 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
@ -405,7 +516,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
},
'gemini-2.0-flash-lite-preview-02-05': {
contextWindow: 1_048_576,
maxOutputTokens: null, // 8_192,
maxOutputTokens: 8_192, // 8_192,
cost: { input: 0.075, output: 0.30 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
@ -414,7 +525,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
},
'gemini-1.5-flash': {
contextWindow: 1_048_576,
maxOutputTokens: null, // 8_192,
maxOutputTokens: 8_192, // 8_192,
cost: { input: 0.075, output: 0.30 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now
supportsFIM: false,
supportsSystemMessage: 'system-role',
@ -423,7 +534,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
},
'gemini-1.5-pro': {
contextWindow: 2_097_152,
maxOutputTokens: null, // 8_192,
maxOutputTokens: 8_192,
cost: { input: 1.25, output: 5.00 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now
supportsFIM: false,
supportsSystemMessage: 'system-role',
@ -432,7 +543,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
},
'gemini-1.5-flash-8b': {
contextWindow: 1_048_576,
maxOutputTokens: null, // 8_192,
maxOutputTokens: 8_192,
cost: { input: 0.0375, output: 0.15 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now
supportsFIM: false,
supportsSystemMessage: 'system-role',
@ -453,13 +564,13 @@ const deepseekModelOptions = {
'deepseek-chat': {
...openSourceModelOptions_assumingOAICompat.deepseekR1,
contextWindow: 64_000, // https://api-docs.deepseek.com/quick_start/pricing
maxOutputTokens: null, // 8_000,
maxOutputTokens: 8_000, // 8_000,
cost: { cache_read: .07, input: .27, output: 1.10, },
},
'deepseek-reasoner': {
...openSourceModelOptions_assumingOAICompat.deepseekCoderV2,
contextWindow: 64_000,
maxOutputTokens: null, // 8_000,
maxOutputTokens: 8_000, // 8_000,
cost: { cache_read: .14, input: .55, output: 2.19, },
},
} as const satisfies { [s: string]: ModelOptions }
@ -478,7 +589,7 @@ const deepseekSettings: ProviderSettings = {
const groqModelOptions = { // https://console.groq.com/docs/models, https://groq.com/pricing/
'llama-3.3-70b-versatile': {
contextWindow: 128_000,
maxOutputTokens: null, // 32_768,
maxOutputTokens: 32_768, // 32_768,
cost: { input: 0.59, output: 0.79 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
@ -487,7 +598,7 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq
},
'llama-3.1-8b-instant': {
contextWindow: 128_000,
maxOutputTokens: null, // 8_192,
maxOutputTokens: 8_192,
cost: { input: 0.05, output: 0.08 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
@ -554,6 +665,42 @@ const openaiCompatible: ProviderSettings = {
// ---------------- OPENROUTER ----------------
const openRouterModelOptions_assumingOpenAICompat = {
'mistralai/mistral-small-3.1-24b-instruct:free': {
contextWindow: 128_000,
maxOutputTokens: null,
cost: { input: 0, output: 0 },
supportsFIM: false,
supportsTools: 'openai-style',
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'google/gemini-2.0-flash-lite-preview-02-05:free': {
contextWindow: 1_048_576,
maxOutputTokens: null,
cost: { input: 0, output: 0 },
supportsFIM: false,
supportsTools: 'openai-style',
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'google/gemini-2.0-pro-exp-02-05:free': {
contextWindow: 1_048_576,
maxOutputTokens: null,
cost: { input: 0, output: 0 },
supportsFIM: false,
supportsTools: 'openai-style',
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'google/gemini-2.0-flash-exp:free': {
contextWindow: 1_048_576,
maxOutputTokens: null,
cost: { input: 0, output: 0 },
supportsFIM: false,
supportsTools: 'openai-style',
supportsSystemMessage: 'system-role',
reasoningCapabilities: false,
},
'deepseek/deepseek-r1': {
...openSourceModelOptions_assumingOAICompat.deepseekR1,
contextWindow: 128_000,
@ -660,8 +807,10 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: ProviderSetting
ollama: ollamaSettings,
openAICompatible: openaiCompatible,
// TODO!!!
// googleVertex: {},
// microsoftAzure: {},
// openHands: {},
} as const
@ -669,8 +818,16 @@ const modelSettingsOfProvider: { [providerName in ProviderName]: ProviderSetting
// returns the capabilities and the adjusted modelName if it was a fallback
export const getModelCapabilities = (providerName: ProviderName, modelName: string): ModelOptions & { modelName: string; isUnrecognizedModel: boolean } => {
const lowercaseModelName = modelName.toLowerCase()
const { modelOptions, modelOptionsFallback } = modelSettingsOfProvider[providerName]
if (modelName in modelOptions) return { modelName, ...modelOptions[modelName], isUnrecognizedModel: false }
// search model options object directly first
for (const modelName_ in modelOptions) {
const lowercaseModelName_ = modelName_.toLowerCase()
if (lowercaseModelName === lowercaseModelName_)
return { modelName, ...modelOptions[modelName], isUnrecognizedModel: false }
}
const result = modelOptionsFallback(modelName)
if (result) return { ...result, isUnrecognizedModel: false }
return { modelName, ...modelOptionsDefaults, isUnrecognizedModel: true }
@ -691,15 +848,18 @@ export type SendableReasoningInfo = {
export const getIsResoningEnabledState = (
export const getIsReasoningEnabledState = (
featureName: FeatureName,
providerName: ProviderName,
modelName: string,
modelSelectionOptions: ModelSelectionOptions | undefined,
) => {
const { supportsReasoning } = getModelCapabilities(providerName, modelName).reasoningCapabilities || {}
const { supportsReasoning, canTurnOffReasoning } = getModelCapabilities(providerName, modelName).reasoningCapabilities || {}
if (!supportsReasoning) return false
const defaultEnabledVal = true // if can't toggle reasoning, then this must be true. just true as default
// default to enabled if can't turn off, or if the featureName is Chat.
const defaultEnabledVal = featureName === 'Chat' || !canTurnOffReasoning
const isReasoningEnabled = modelSelectionOptions?.reasoningEnabled ?? defaultEnabledVal
return isReasoningEnabled
}
@ -707,6 +867,7 @@ export const getIsResoningEnabledState = (
// used to force reasoning state (complex) into something simple we can just read from when sending a message
export const getSendableReasoningInfo = (
featureName: FeatureName,
providerName: ProviderName,
modelName: string,
modelSelectionOptions: ModelSelectionOptions | undefined,
@ -714,7 +875,7 @@ export const getSendableReasoningInfo = (
const { canIOReasoning, reasoningBudgetSlider } = getModelCapabilities(providerName, modelName).reasoningCapabilities || {}
if (!canIOReasoning) return null
const isReasoningEnabled = getIsResoningEnabledState(providerName, modelName, modelSelectionOptions)
const isReasoningEnabled = getIsReasoningEnabledState(featureName, providerName, modelName, modelSelectionOptions)
if (!isReasoningEnabled) return null
// check for reasoning budget

View file

@ -3,15 +3,12 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { URI } from '../../../../../base/common/uri.js';
import { os } from '../helpers/systemInfo.js';
import { CodeSelection, FileSelection, StagingSelectionItem } from '../chatThreadServiceTypes.js';
import { StagingSelectionItem } from '../chatThreadServiceTypes.js';
import { ChatMode } from '../voidSettingsTypes.js';
import { InternalToolInfo } from '../toolsServiceTypes.js';
import { IVoidModelService } from '../voidModelService.js';
import { EndOfLinePreference } from '../../../../../editor/common/model.js';
import { InternalToolInfo } from '../toolsServiceTypes.js';
// this is just for ease of readability
export const tripleTick = ['```', '```']
@ -42,10 +39,21 @@ ${tripleTick[1]}`
// ======================================================== tools ========================================================
const paginationHelper = {
desc: `Very large results may be paginated (indicated in the result). Pagination fails gracefully if out of bounds or invalid page number.`,
desc: `Very large results may be paginated (a note will always be included if pagination took place). Pagination fails gracefully if out of bounds or invalid page number.`,
param: { pageNumber: { type: 'number', description: 'The page number (default is the first page = 1).' }, }
} as const
const uriParam = (object: string) => ({
uri: { type: 'string', description: `The FULL path to the ${object}.` }
})
const searchParams = {
searchInFolder: { type: 'string', description: 'Only search files in this given folder. Leave as empty to search all available files.' },
isRegex: { type: 'string', description: 'Whether to treat the query as a regular expression. Default is "false".' },
} as const
export const voidTools = {
// --- context-gathering (read/search/list) ---
@ -53,62 +61,74 @@ export const voidTools = {
name: 'read_file',
description: `Returns file contents of a given URI. ${paginationHelper.desc}`,
params: {
uri: { type: 'string', description: undefined },
...uriParam('file'),
startLine: { type: 'string', description: 'Line to start reading from. Default is "null", treated as 1.' },
endLine: { type: 'string', description: 'Line to stop reading from (inclusive). Default is "null", treated as Infinity.' },
...paginationHelper.param,
},
},
list_dir: {
name: 'list_dir',
description: `Returns all file names and folder names in a given URI. ${paginationHelper.desc}`,
ls_dir: {
name: 'ls_dir',
description: `Returns all file names and folder names in a given folder. ${paginationHelper.desc}`,
params: {
uri: { type: 'string', description: undefined },
...uriParam('folder'),
...paginationHelper.param,
},
},
pathname_search: {
name: 'pathname_search',
description: `Returns all pathnames that match a given \`find\`-style query (searches ONLY file names). You should use this when looking for a file with a specific name or path. ${paginationHelper.desc}`,
get_dir_structure: {
name: 'get_dir_structure',
description: `This is a very effective way to learn about the user's codebase. You might want to use this instead of ls_dir. Returns a tree diagram of all the files and folders in the given folder URI. If results are large, the given string will be truncated (this will be indicated), in which case you might want to call this tool on a lower folder to get better results, or just use ls_dir which supports pagination.`,
params: {
...uriParam('folder')
}
},
search_pathnames_only: {
name: 'search_pathnames_only',
description: `Returns all pathnames that match a given query (searches ONLY file names). You should use this when looking for a file with a specific name or path. ${paginationHelper.desc}`,
params: {
query: { type: 'string', description: undefined },
...searchParams,
...paginationHelper.param,
},
},
grep_search: {
name: 'grep_search',
search_files: {
name: 'search_files',
description: `Returns all pathnames that match a given \`grep\`-style query (searches ONLY file contents). The query can be any regex. This is often followed by the \`read_file\` tool to view the full file contents of results. ${paginationHelper.desc}`,
params: {
query: { type: 'string', description: undefined },
...searchParams,
...paginationHelper.param,
},
},
// --- editing (create/delete) ---
create_uri: {
name: 'create_uri',
create_file_or_folder: {
name: 'create_file_or_folder',
description: `Create a file or folder at the given path. To create a folder, ensure the path ends with a trailing slash. Fails gracefully if the file already exists. Missing ancestors in the path will be recursively created automatically.`,
params: {
uri: { type: 'string', description: undefined },
...uriParam('file or folder'),
},
},
delete_uri: {
name: 'delete_uri',
delete_file_or_folder: {
name: 'delete_file_or_folder',
description: `Delete a file or folder at the given path. Fails gracefully if the file or folder does not exist.`,
params: {
uri: { type: 'string', description: undefined },
params: { type: 'string', description: 'Return -r here to delete this URI and all descendants (if applicable). Default is the empty string.' }
...uriParam('file or folder'),
params: { type: 'string', description: 'Return -r here to delete recursively (if applicable). Default is the empty string.' }
},
},
edit: { // APPLY TOOL
name: 'edit',
edit_file: { // APPLY TOOL
name: 'edit_file',
description: `Edits the contents of a file, given the file's URI and a description. Fails gracefully if the file does not exist.`,
params: {
uri: { type: 'string', description: undefined },
...uriParam('file'),
changeDescription: {
type: 'string', description: `\
- Your changeDescription should be a brief code description of the change you want to make, with comments like "// ... existing code ..." to condense your writing.
@ -120,11 +140,11 @@ Here's an example of a good description:\n${editToolDescription}.`
},
},
terminal_command: {
name: 'terminal_command',
run_terminal_command: {
name: 'run_terminal_command',
description: `Executes a terminal command.`,
params: {
command: { type: 'string', description: 'The terminal command to execute.' },
command: { type: 'string', description: 'The terminal command to execute. Typically you should pipe to cat to avoid pagination.' },
waitForCompletion: { type: 'string', description: `Whether or not to await the command to complete and get the final result. Default is true. Make this value false when you want a command to run indefinitely without waiting for it.` },
terminalId: { type: 'string', description: 'Optional (value must be an integer >= 1, or empty which will go with the default). This is the ID of the terminal instance to execute the command in. The primary purpose of this is to start a new terminal for background processes or tasks that run indefinitely (e.g. if you want to run a server locally). Fails gracefully if a terminal ID does not exist, by creating a new terminal instance. Defaults to the preferred terminal ID.' },
},
@ -144,8 +164,8 @@ Here's an example of a good description:\n${editToolDescription}.`
export const chat_systemMessage = (workspaces: string[], runningTerminalIds: string[], mode: ChatMode) => `\
You are an expert coding ${mode === 'agent' ? 'agent' : 'assistant'} that runs in the Void code editor. Your job is \
export const chat_systemMessage = ({ workspaceFolders, openedURIs, activeURI, runningTerminalIds, directoryStr, chatMode: mode }: { workspaceFolders: string[], directoryStr: string, openedURIs: string[], activeURI: string | undefined, runningTerminalIds: string[], chatMode: ChatMode }) => `\
You are an expert coding ${mode === 'agent' ? 'agent' : 'assistant'} that runs in the user's IDE called Void. Your job is \
${mode === 'agent' ? `to help the user develop, run, deploy, and make changes to their codebase. You should ALWAYS bring user's task to completion to the fullest extent possible, calling tools to make all necessary changes.`
: mode === 'gather' ? `to search and understand the user's codebase. You MUST use tools to read files and help the user understand the codebase, even if you were initially given files.`
: mode === 'normal' ? `to assist the user with their coding tasks.`
@ -155,10 +175,12 @@ Please assist the user with their query. The user's query is never invalid.
${/* system info */''}
The user's system information is as follows:
- ${os}
- Open workspace(s): ${workspaces.join(', ') || 'NO WORKSPACE OPEN'}
${(mode === 'agent') && runningTerminalIds.length !== 0 ? `\
- Existing terminal IDs: ${runningTerminalIds.join(', ')}
`: '\n'}
- Open workspace(s): ${workspaceFolders.join(', ') || 'NO WORKSPACE OPEN'}
- Open tab(s): ${openedURIs.join(', ') || 'NO OPENED EDITORS'}
- Active tab: ${activeURI}
${(mode === 'agent') && runningTerminalIds.length !== 0 ? `
- Existing terminal IDs: ${runningTerminalIds.join(', ')}` : ''}
${/* tool use */ mode === 'agent' || mode === 'gather' ? `\
You will be given tools you can call.
${mode === 'agent' ? `\
@ -189,109 +211,134 @@ If you think it's appropriate to suggest an edit to a file, then you must descri
- The remaining contents should be a brief code description of the change you want to make, with comments like "// ... existing code ..." to condense your writing.
- NEVER re-write the whole file, and ALWAYS use comments like "// ... existing code ...". Bias towards writing as little as possible.
- Your description will be handed to a dumber, faster model that will quickly apply the change, so it should be clear and concise.
Here's an example of a good code block:\n${fileNameEdit}.\
Here's an example of a good code block:\n${fileNameEdit}.
If you write a code block that's related to a specific file, please use the same format as above:
- The first line of the code block must be the FULL PATH of the related file if known.
- The remaining contents of the file should proceed as usual.
\
`}
${/* misc */''}
Misc:
- Do not make things up.
- Do not be lazy.
- NEVER re-write the entire file.
- Always wrap any code you produce in triple backticks, and specify a language if possible. For example, ${tripleTick[0]}typescript\n...\n${tripleTick[1]}.\
- Always wrap any code you produce in triple backticks, and specify a language if possible. For example, ${tripleTick[0]}typescript\n...\n${tripleTick[1]}.
- Today's date is ${new Date().toDateString()}
The user's codebase is structured as follows:\n${directoryStr}
\
`
// agent mode doesn't know about 1st line paths yet
// - If you wrote triple ticks and ___, then include the file's full path in the first line of the triple ticks. This is only for display purposes to the user, and it's preferred but optional. Never do this in a tool parameter, or if there's ambiguity about the full path.
type FileSelnLocal = { fileURI: URI, language: string, content: string }
const stringifyFileSelection = ({ fileURI, language, content }: FileSelnLocal) => {
return `\
${fileURI.fsPath}
${tripleTick[0]}${language}
${content}
${tripleTick[1]}
`
}
const stringifyCodeSelection = ({ fileURI, language, selectionStr, range }: CodeSelection) => {
return `\
${fileURI.fsPath} (lines ${range.startLineNumber}:${range.endLineNumber})
${tripleTick[0]}${language}
${selectionStr}
${tripleTick[1]}
`
}
// type FileSelnLocal = { fileURI: URI, language: string, content: string }
// const stringifyFileSelection = ({ fileURI, language, content }: FileSelnLocal) => {
// return `\
// ${fileURI.fsPath}
// ${tripleTick[0]}${language}
// ${content}
// ${tripleTick[1]}
// `
// }
// const stringifyCodeSelection = ({ uri, language, range }: StagingSelectionItem & { type: 'CodeSelection' }) => {
// return `\
const failToReadStr = 'Could not read content. This file may have been deleted. If you expected content here, you can tell the user about this as they might not know.'
const stringifyFileSelections = async (fileSelections: FileSelection[], voidModelService: IVoidModelService) => {
if (fileSelections.length === 0) return null
const fileSlns: FileSelnLocal[] = await Promise.all(fileSelections.map(async (sel) => {
const { model } = await voidModelService.getModelSafe(sel.fileURI)
const content = model?.getValue(EndOfLinePreference.LF) ?? failToReadStr
return { ...sel, content }
}))
return fileSlns.map(sel => stringifyFileSelection(sel)).join('\n')
}
// ${tripleTick[0]}${language}
// ${selectionStr}
// ${tripleTick[1]}
// `
// }
// const failToReadStr = 'Could not read content. This file may have been deleted. If you expected content here, you can tell the user about this as they might not know.'
// const stringifyFileSelections = async (fileSelections: FileSelection[], voidModelService: IVoidModelService) => {
// if (fileSelections.length === 0) return null
// const fileSlns: FileSelnLocal[] = await Promise.all(fileSelections.map(async (sel) => {
// const { model } = await voidModelService.getModelSafe(sel.fileURI)
// const content = model?.getValue(EndOfLinePreference.LF) ?? failToReadStr
// return { ...sel, content }
// }))
// return fileSlns.map(sel => stringifyFileSelection(sel)).join('\n')
// }
const stringifyCodeSelections = (codeSelections: CodeSelection[]) => {
return codeSelections.map(sel => {
stringifyCodeSelection(sel)
}).join('\n') || null
}
const stringifySelectionNames = (currSelns: StagingSelectionItem[] | null): string => {
if (!currSelns) return ''
return currSelns.map(s => `${s.fileURI.fsPath}${s.range ? ` (lines ${s.range.startLineNumber}:${s.range.endLineNumber})` : ''}`).join('\n')
}
export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null) => {
// export const chat_selectionsString = async (
// prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null,
// voidModelService: IVoidModelService,
// ) => {
const selnsStr = stringifySelectionNames(currSelns)
// // ADD IN FILES AT TOP
// const allSelections = [...currSelns || [], ...prevSelns || []]
let str = ''
if (selnsStr) { str += `SELECTIONS\n${selnsStr}\n` }
str += `\nINSTRUCTIONS\n${instructions}`
return str;
};
// if (allSelections.length === 0) return null
export const chat_selectionsString = async (
prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null,
voidModelService: IVoidModelService,
// for (const selection of allSelections) {
// if (selection.type === 'Selection') {
// codeSelections.push(selection)
// }
// else if (selection.type === 'File') {
// const fileSelection = selection
// const path = fileSelection.fileURI.fsPath
// if (!filesURIs.has(path)) {
// filesURIs.add(path)
// fileSelections.push(fileSelection)
// }
// }
// }
// const filesStr = await stringifyFileSelections(fileSelections, voidModelService)
// const selnsStr = stringifyCodeSelections(codeSelections)
// const fileContents = [filesStr, selnsStr].filter(Boolean).join('\n')
// return fileContents || null
// }
// export const chat_lastUserMessageWithFilesAdded = (userMessage: string, selectionsString: string | null) => {
// if (userMessage) return `${userMessage}${selectionsString ? `\n${selectionsString}` : ''}`
// else return userMessage
// }
export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null,
opts: { type: 'references' } | { type: 'fullCode', voidModelService: IVoidModelService }
) => {
// ADD IN FILES AT TOP
const allSelections = [...currSelns || [], ...prevSelns || []]
const lineNumAddition = (range: [number, number]) => ` (lines ${range[0]}:${range[1]})`
let selnsStrs: string[] = []
if (opts.type === 'references') {
selnsStrs = currSelns?.map((s) => {
if (s.type === 'File') return `${s.uri.fsPath}`
if (s.type === 'CodeSelection') return `${s.uri.fsPath}${lineNumAddition(s.range)}`
if (s.type === 'Folder') return `${s.uri.fsPath}/`
return ''
}) ?? []
}
if (opts.type === 'fullCode') {
selnsStrs = await Promise.all(currSelns?.map(async (s) => {
if (s.type === 'File' || s.type === 'CodeSelection') {
const voidModelService = opts.voidModelService
const { model } = await voidModelService.getModelSafe(s.uri)
if (!model) return ''
const val = model.getValue(EndOfLinePreference.LF)
if (allSelections.length === 0) return null
const codeSelections: CodeSelection[] = []
const fileSelections: FileSelection[] = []
const filesURIs = new Set<string>()
for (const selection of allSelections) {
if (selection.type === 'Selection') {
codeSelections.push(selection)
}
else if (selection.type === 'File') {
const fileSelection = selection
const path = fileSelection.fileURI.fsPath
if (!filesURIs.has(path)) {
filesURIs.add(path)
fileSelections.push(fileSelection)
const lineNumAdd = s.type === 'CodeSelection' ? lineNumAddition(s.range) : ''
const str = `${s.uri.fsPath}${lineNumAdd}\n${tripleTick[0]}${s.language}\n${val}\n${tripleTick[1]}`
return str
}
}
if (s.type === 'Folder') {
// TODO
return ''
}
return ''
}) ?? [])
}
const filesStr = await stringifyFileSelections(fileSelections, voidModelService)
const selnsStr = stringifyCodeSelections(codeSelections)
const fileContents = [filesStr, selnsStr].filter(Boolean).join('\n')
return fileContents || null
}
export const chat_lastUserMessageWithFilesAdded = (userMessage: string, selectionsString: string | null) => {
if (userMessage) return `${userMessage}${selectionsString ? `\n${selectionsString}` : ''}`
else return userMessage
const selnsStr = selnsStrs.join('\n') ?? ''
let str = ''
str += `${instructions}`
if (selnsStr) str += `\n---\nSELECTIONS\n${selnsStr}`
return str;
}

View file

@ -0,0 +1,19 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
// past values:
// 'void.settingsServiceStorage'
// 'void.settingsServiceStorageI' // 1.0.2
// 1.0.3
export const VOID_SETTINGS_STORAGE_KEY = 'void.settingsServiceStorageII'
// past values:
// 'void.chatThreadStorage'
// 'void.chatThreadStorageI' // 1.0.2
// 1.0.3
export const THREAD_STORAGE_KEY = 'void.chatThreadStorageII'

View file

@ -2,18 +2,18 @@ import { URI } from '../../../../base/common/uri.js'
import { voidTools } from './prompt/prompts.js';
export type ToolDirectoryItem = {
export type TerminalResolveReason = { type: 'toofull' | 'timeout' | 'bgtask' } | { type: 'done', exitCode: number }
// Partial of IFileStat
export type ShallowDirectoryItem = {
uri: URI;
name: string;
isDirectory: boolean;
isSymbolicLink: boolean;
}
export type TerminalResolveReason = { type: 'toofull' | 'timeout' | 'bgtask' } | { type: 'done', exitCode: number }
// we do this using Anthropic's style and convert to OpenAI style later
export type InternalToolInfo = {
name: string,
@ -36,32 +36,36 @@ export const isAToolName = (toolName: string): toolName is ToolName => {
}
const toolNamesWithApproval = ['create_uri', 'delete_uri', 'edit', 'terminal_command'] as const satisfies readonly ToolName[]
const toolNamesWithApproval = ['create_file_or_folder', 'delete_file_or_folder', 'edit_file', 'run_terminal_command'] as const satisfies readonly ToolName[]
export type ToolNameWithApproval = typeof toolNamesWithApproval[number]
export const toolNamesThatRequireApproval = new Set<ToolName>(toolNamesWithApproval)
// PARAMS OF TOOL CALL
export type ToolCallParams = {
'read_file': { uri: URI, pageNumber: number },
'list_dir': { rootURI: URI, pageNumber: number },
'pathname_search': { queryStr: string, pageNumber: number },
'grep_search': { queryStr: string, pageNumber: number },
'read_file': { uri: URI, startLine: number | null, endLine: number | null, pageNumber: number },
'ls_dir': { rootURI: URI, pageNumber: number },
'get_dir_structure': { rootURI: URI },
'search_pathnames_only': { queryStr: string, include: string | null, pageNumber: number },
'search_files': { queryStr: string, isRegex: boolean, searchInFolder: URI | null, pageNumber: number },
// ---
'edit': { uri: URI, changeDescription: string },
'create_uri': { uri: URI, isFolder: boolean },
'delete_uri': { uri: URI, isRecursive: boolean, isFolder: boolean },
'terminal_command': { command: string, proposedTerminalId: string, waitForCompletion: boolean },
'edit_file': { uri: URI, changeDescription: string },
'create_file_or_folder': { uri: URI, isFolder: boolean },
'delete_file_or_folder': { uri: URI, isRecursive: boolean, isFolder: boolean },
'run_terminal_command': { command: string, proposedTerminalId: string, waitForCompletion: boolean },
}
// RESULT OF TOOL CALL
export type ToolResultType = {
'read_file': { fileContents: string, hasNextPage: boolean },
'list_dir': { children: ToolDirectoryItem[] | null, hasNextPage: boolean, hasPrevPage: boolean, itemsRemaining: number },
'pathname_search': { uris: URI[], hasNextPage: boolean },
'grep_search': { uris: URI[], hasNextPage: boolean },
'ls_dir': { children: ShallowDirectoryItem[] | null, hasNextPage: boolean, hasPrevPage: boolean, itemsRemaining: number },
'get_dir_structure': { str: string, },
'search_pathnames_only': { uris: URI[], hasNextPage: boolean },
'search_files': { uris: URI[], hasNextPage: boolean },
// ---
'edit': Promise<void>,
'create_uri': {},
'delete_uri': {},
'terminal_command': { terminalId: string, didCreateTerminal: boolean, result: string; resolveReason: TerminalResolveReason; },
'edit_file': Promise<{ lintErrorsStr: string | null }>,
'create_file_or_folder': {},
'delete_file_or_folder': {},
'run_terminal_command': { terminalId: string, didCreateTerminal: boolean, result: string; resolveReason: TerminalResolveReason; },
}

View file

@ -14,6 +14,7 @@ export interface IVoidModelService {
readonly _serviceBrand: undefined;
initializeModel(uri: URI): Promise<void>;
getModel(uri: URI): VoidModelType;
getModelFromFsPath(fsPath: string): VoidModelType;
getModelSafe(uri: URI): Promise<VoidModelType>;
}
@ -37,8 +38,8 @@ class VoidModelService extends Disposable implements IVoidModelService {
this._modelRefOfURI[uri.fsPath] = editorModelRef;
};
getModel = (uri: URI): VoidModelType => {
const editorModelRef = this._modelRefOfURI[uri.fsPath];
getModelFromFsPath = (fsPath: string): VoidModelType => {
const editorModelRef = this._modelRefOfURI[fsPath];
if (!editorModelRef) {
return { model: null, editorModel: null };
}
@ -52,6 +53,11 @@ class VoidModelService extends Disposable implements IVoidModelService {
return { model, editorModel: editorModelRef.object };
};
getModel = (uri: URI) => {
return this.getModelFromFsPath(uri.fsPath)
}
getModelSafe = async (uri: URI): Promise<VoidModelType> => {
if (!(uri.fsPath in this._modelRefOfURI)) await this.initializeModel(uri);
return this.getModel(uri);

View file

@ -12,13 +12,9 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
import { IMetricsService } from './metricsService.js';
import { getModelCapabilities } from './modelCapabilities.js';
import { VOID_SETTINGS_STORAGE_KEY } from './storageKeys.js';
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, defaultProviderSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode } from './voidSettingsTypes.js';
// past values:
// 'void.settingsServiceStorage'
const STORAGE_KEY = 'void.settingsServiceStorageI'
// name is the name in the dropdown
export type ModelOption = { name: string, selection: ModelSelection }
@ -38,7 +34,7 @@ type SetModelSelectionOfFeatureFn = <K extends FeatureName>(
type SetGlobalSettingFn = <T extends GlobalSettingName>(settingName: T, newVal: GlobalSettings[T]) => void;
type SetOptionsOfModelSelection = (providerName: ProviderName, modelName: string, newVal: Partial<ModelSelectionOptions>) => void
type SetOptionsOfModelSelection = (featureName: FeatureName, providerName: ProviderName, modelName: string, newVal: Partial<ModelSelectionOptions>) => void
export type VoidSettingsState = {
@ -177,7 +173,7 @@ const defaultState = () => {
settingsOfProvider: deepClone(defaultSettingsOfProvider),
modelSelectionOfFeature: { 'Chat': null, 'Ctrl+K': null, 'Autocomplete': null, 'Apply': null },
globalSettings: deepClone(defaultGlobalSettings),
optionsOfModelSelection: {},
optionsOfModelSelection: { 'Chat': {}, 'Ctrl+K': {}, 'Autocomplete': {}, 'Apply': {} },
_modelOptions: [], // computed later
}
return d
@ -227,7 +223,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
private async _readState(): Promise<VoidSettingsState> {
const encryptedState = this._storageService.get(STORAGE_KEY, StorageScope.APPLICATION)
const encryptedState = this._storageService.get(VOID_SETTINGS_STORAGE_KEY, StorageScope.APPLICATION)
if (!encryptedState)
return defaultState()
@ -240,7 +236,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
private async _storeState() {
const state = this.state
const encryptedState = await this._encryptionService.encrypt(JSON.stringify(state))
this._storageService.store(STORAGE_KEY, encryptedState, StorageScope.APPLICATION, StorageTarget.USER);
this._storageService.store(VOID_SETTINGS_STORAGE_KEY, encryptedState, StorageScope.APPLICATION, StorageTarget.USER);
}
setSettingOfProvider: SetSettingOfProviderFn = async (providerName, settingName, newVal) => {
@ -318,16 +314,19 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
}
setOptionsOfModelSelection = async (providerName: ProviderName, modelName: string, newVal: Partial<ModelSelectionOptions>) => {
setOptionsOfModelSelection = async (featureName: FeatureName, providerName: ProviderName, modelName: string, newVal: Partial<ModelSelectionOptions>) => {
const newState: VoidSettingsState = {
...this.state,
optionsOfModelSelection: {
...this.state.optionsOfModelSelection,
[providerName]: {
...this.state.optionsOfModelSelection[providerName],
[modelName]: {
...this.state.optionsOfModelSelection[providerName]?.[modelName],
...newVal
[featureName]: {
...this.state.optionsOfModelSelection[featureName],
[providerName]: {
...this.state.optionsOfModelSelection[featureName][providerName],
[modelName]: {
...this.state.optionsOfModelSelection[featureName][providerName]?.[modelName],
...newVal
}
}
}
}

View file

@ -422,4 +422,11 @@ export type ModelSelectionOptions = {
reasoningBudget?: number;
}
export type OptionsOfModelSelection = Partial<{ [providerName in ProviderName]: { [modelName: string]: ModelSelectionOptions | undefined } }>
export type OptionsOfModelSelection = {
[featureName in FeatureName]: Partial<{
[providerName in ProviderName]: {
[modelName: string]:
ModelSelectionOptions | undefined
}
}>
}

View file

@ -40,17 +40,10 @@ const prepareMessages_normalize = ({ messages: messages_ }: { messages: LLMChatM
const newMessages: LLMChatMessage[] = []
if (messages.length >= 0) newMessages.push(messages[0])
// remove duplicate roles
// remove duplicate roles - we used to do this, but we don't anymore
for (let i = 1; i < messages.length; i += 1) {
const curr = messages[i]
// const prev = messages[i - 1]
// // if found a repeated role, put the current content in the prev
// if ((curr.role === 'assistant' && prev.role === 'assistant')) {
// prev.content += '\n' + curr.content
// continue
// }
// add the message
newMessages.push(curr)
const m = messages[i]
newMessages.push(m)
}
const finalMessages = newMessages.map(m => ({ ...m, content: m.content.trim() }))
return { messages: finalMessages }
@ -61,6 +54,95 @@ const prepareMessages_normalize = ({ messages: messages_ }: { messages: LLMChatM
const CHARS_PER_TOKEN = 4
const TRIM_TO_LEN = 60
const prepareMessages_fitIntoContext = ({ messages, contextWindow, maxOutputTokens }: { messages: LLMChatMessage[], contextWindow: number, maxOutputTokens: number }): { messages: LLMChatMessage[] } => {
// the higher the weight, the higher the desire to truncate
const alreadyTrimmedIdxes = new Set<number>()
const weight = (message: LLMChatMessage, messages: LLMChatMessage[], idx: number) => {
const base = message.content.length
let multiplier: number
if (message.role === 'system')
return 0 // never erase system message
multiplier = 1 + (messages.length - 1 - idx) / messages.length // slow rampdown from 2 to 1 as index increases
if (message.role === 'user') {
multiplier *= 1
}
else {
multiplier *= 10 // llm tokens are far less valuable than user tokens
}
// 1st message, last 3 msgs, any already modified message should be low in weight
if (idx === 0 || idx >= messages.length - 1 - 3 || alreadyTrimmedIdxes.has(idx)) {
multiplier *= .05
}
return base * multiplier
}
const _findLargestByWeight = (messages: LLMChatMessage[]) => {
let largestIndex = -1
let largestWeight = -Infinity
for (let i = 0; i < messages.length; i += 1) {
const m = messages[i]
const w = weight(m, messages, i)
if (w > largestWeight) {
largestWeight = w
largestIndex = i
}
}
return largestIndex
}
let totalLen = 0
for (const m of messages) { totalLen += m.content.length }
const charsNeedToTrim = totalLen - (contextWindow - maxOutputTokens) * CHARS_PER_TOKEN
if (charsNeedToTrim <= 0) return { messages }
// <----------------------------------------->
// 0 | | |
// | contextWindow |
// contextWindow - maxOut|putTokens
// |
// totalLen
// TRIM HIGHEST WEIGHT MESSAGES
let remainingCharsToTrim = charsNeedToTrim
let i = 0
while (remainingCharsToTrim > 0) {
i += 1
if (i > 100) break
const trimIdx = _findLargestByWeight(messages)
const m = messages[trimIdx]
// if can finish here, do
const numCharsWillTrim = m.content.length - TRIM_TO_LEN
if (numCharsWillTrim > remainingCharsToTrim) {
m.content = m.content.slice(0, m.content.length - remainingCharsToTrim)
break
}
remainingCharsToTrim -= numCharsWillTrim
m.content = m.content.substring(0, TRIM_TO_LEN - 3) + '...'
alreadyTrimmedIdxes.add(trimIdx)
}
return { messages }
}
// no matter whether the model supports a system message or not (or what format it supports), add it in some way
const prepareMessages_systemMessage = ({
@ -378,14 +460,21 @@ export const prepareMessages = ({
supportsSystemMessage,
supportsTools,
supportsAnthropicReasoningSignature,
contextWindow,
maxOutputTokens,
}: {
messages: LLMChatMessage[],
aiInstructions: string,
supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated',
supportsTools: false | 'anthropic-style' | 'openai-style',
supportsAnthropicReasoningSignature: boolean,
contextWindow: number,
maxOutputTokens: number | null | undefined,
}) => {
const { messages: messages1 } = prepareMessages_normalize({ messages })
maxOutputTokens = maxOutputTokens ?? 4_096 // default to 4096
const { messages: messages0 } = prepareMessages_normalize({ messages })
const { messages: messages1 } = prepareMessages_fitIntoContext({ messages: messages0, contextWindow, maxOutputTokens })
const { messages: messages2 } = prepareMessages_anthropicContent({ messages: messages1, supportsAnthropicReasoningSignature })
const { messages: messages3, separateSystemMessageStr } = prepareMessages_systemMessage({ messages: messages2, aiInstructions, supportsSystemMessage })
const { messages: messages4 } = prepareMessages_tools({ messages: messages3, supportsTools })

View file

@ -157,7 +157,8 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
modelName,
supportsSystemMessage,
supportsTools,
// maxOutputTokens,
contextWindow,
maxOutputTokens,
reasoningCapabilities,
} = getModelCapabilities(providerName, modelName_)
@ -165,7 +166,7 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
// reasoning
const { canIOReasoning, openSourceThinkTags, } = reasoningCapabilities || {}
const reasoningInfo = getSendableReasoningInfo(providerName, modelName_, modelSelectionOptions) // user's modelName_ here
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
// tools
@ -173,10 +174,10 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
const toolsObj = tools ? { tools: tools, tool_choice: 'auto', parallel_tool_calls: false, } as const : {}
// max tokens
// const maxTokens = reasoningInfo?.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens
const maxTokens = reasoningInfo?.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens
// instance
const { messages } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicReasoningSignature: false })
const { messages } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicReasoningSignature: false, contextWindow, maxOutputTokens: maxTokens })
const openai: OpenAI = newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload })
const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: modelName,
@ -316,6 +317,7 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
const {
modelName,
supportsSystemMessage,
contextWindow,
supportsTools,
maxOutputTokens,
reasoningCapabilities,
@ -325,7 +327,7 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
const { providerReasoningIOSettings } = getProviderCapabilities(providerName)
// reasoning
const reasoningInfo = getSendableReasoningInfo(providerName, modelName_, modelSelectionOptions) // user's modelName_ here
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
// tools
@ -339,7 +341,7 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
const maxTokens = reasoningInfo?.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens
// instance
const { messages, separateSystemMessageStr } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicReasoningSignature: true })
const { messages, separateSystemMessageStr } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicReasoningSignature: true, contextWindow, maxOutputTokens: maxTokens })
const anthropic = new Anthropic({
apiKey: thisConfig.apiKey,
dangerouslyAllowBrowser: true

View file

@ -13,6 +13,7 @@ import { INativeHostService } from '../../../../platform/native/common/native.js
import { IProductService } from '../../../../platform/product/common/productService.js';
import { process } from '../../../../base/parts/sandbox/electron-sandbox/globals.js';
import { getActiveWindow } from '../../../../base/browser/dom.js';
import { getReleaseString } from '../../../../workbench/common/release.js';
export class NativeDialogHandler extends AbstractDialogHandler {
@ -79,6 +80,7 @@ export class NativeDialogHandler extends AbstractDialogHandler {
}
const osProps = await this.nativeHostService.getOSProperties();
const releaseString = getReleaseString();
const detailString = (useAgo: boolean): string => {
return localize({ key: 'aboutDetail', comment: ['Electron, Chromium, Node.js and V8 are product names that need no translation'] },
@ -93,7 +95,7 @@ export class NativeDialogHandler extends AbstractDialogHandler {
process.versions['node'],
process.versions['v8'],
`${osProps.type} ${osProps.arch} ${osProps.release}${isLinuxSnap ? ' snap' : ''}`
);
).replace('\n', `\n${releaseString} ${this.productService.release || 'Unknown'}\n`);
};
const detail = detailString(true);

View file

@ -255,8 +255,8 @@ export class NativeWindow extends BaseWindow {
label: localize('downloadArmBuild', "Download"),
run: () => {
const quality = this.productService.quality;
const stableURL = 'https://code.visualstudio.com/docs/?dv=osx';
const insidersURL = 'https://code.visualstudio.com/docs/?dv=osx&build=insiders';
const stableURL = 'https://github.com/!!GH_REPO_PATH!!/releases/latest';
const insidersURL = 'https://github.com/!!GH_REPO_PATH!!-insiders/releases/latest';
this.openerService.open(quality === 'stable' ? stableURL : insidersURL);
}
}],